
wait && notify下面的方法,都是 Object 类实现的,所以所有类都存在这些线程同步方法!
方法 | 描述 | 说明 |
|---|---|---|
void wait() | 无限期等待,直到被唤醒 | 必须在 synchronized 中使用 |
void wait(long timeout) | 最多等待 timeout 毫秒 | 超时未被唤醒也会继续执行 |
void notify() | 无参数,随机唤醒一个等待线程 | 只能唤醒一个,不能控制具体唤醒哪个线程 |
void notifyAll() | 无参数,唤醒所有等待该对象锁的线程 | 所有被唤醒线程会竞争锁,推荐用于并发协作 |
wait 做的事情如下所示:
CPU 使用权,释放当前的锁一般 wait() 的使用如下所示:
synchronized (locker) {
// 使用 while 防止 “虚假唤醒”
while (!条件成立) {
locker.wait(); // 🟢 这段逻辑是实现同步的关键一步,应该放在逻辑开头
}
// 当条件满足,才继续做事
}✅注意事项:
wait() 和 notify() 必须在 synchronized 内使用,否则会抛出 IllegalMonitorStateExceptionwhile 而非 if 来包裹 wait(),防止虚假唤醒wait 与 sleep 的区别sleep() | wait() | |
|---|---|---|
所属类 | Thread 类的静态方法 | Object 类的方法 |
是否需要锁(synchronized) | ❌ | ✅ 需要,且必须在同步代码块中使用 |
是否释放锁 | ❌ | ✅ 释放锁,等待期间不占有对象锁 |
是否释放CPU | ✅ | ✅ |
唤醒方式 | 超时唤醒 | 被 notify()/notifyAll() 或超时唤醒 |
是否可中断 | ✅ 抛出 InterruptedException | ✅ 抛出 InterruptedException |
是否用于线程间通信 | ❌ | ✅ 能,用于线程间协调与同步 |
主要用途 | 暂停线程(模拟延迟、限流等) | 条件不满足时挂起线程,等待其他线程通知继续执行 |
单例模式:保证一个类在 JVM 中只存在一个实例,并提供一个全局访问点。
饿汉模式属于 "急切加载" 的方式,即在类加载时就立即创建单例对象,而不管是否会被使用。
public class StarvingSingleton {
// 静态变量,存储单例对象,使用final修饰
private static final StarvingSingleton instance = new StarvingSingleton();
// 私有化构造方法,防止外部通过new创建对象
private StarvingSingleton() {
}
// 提供获取单例成员接口
public static StarvingSingleton getInstance() {
return instance;
}
}💥注意事项:
instance 设为 final,是为了防止其它代码意外修改单例对象的引用,从而避免创建多个实例的情况。懒汉模式的核心思想是 "延迟加载",即只有在第一次使用单例对象时才创建它。这种方式可以节省资源,因为单例对象只有在真正需要时才会被创建!
public class LazySingleton {
// 静态变量,存储单例对象,同时使用volatile修饰
private static volatile LazySingleton instance = null;
// 私有化构造方法,防止外部通过new创建对象
private LazySingleton() {
}
// 提供一个公共的静态方法,返回单例对象
public static LazySingleton getInstance() {
// 第一次检查:提高效率
if(instance == null) {
synchronized(LazySingleton.class) {
// 第二次检查:保证单例
if(instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
}💥注意事项:
instance 设为 volatile,是为了防止编译器优化出现的指令重排序问题,导致上述第 14 行代码拿到的对象是 null(重排序导致先赋值,再初始化,结果拿到的是没初始化前的 null 对象),此时一使用该对象,直接就奔溃了!阻塞队列是一种特殊的队列,也遵守 "先进先出" 的原则,并且阻塞队列也是一种线程安全的数据结构,并且具有以下特性:
阻塞队列的一个典型应用场景就是 "生产者消费者模型”,这是一种非常典型的开发模型,如下图所示:

生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,而是直接扔给阻塞队列;消费者不找生产者要数据,而是直接从阻塞队列里取。
下面是生产者消费者模型的优缺点:
BlockingQueue在 Java 标准库中,java.util.concurrent 包提供了多种阻塞队列的实现,这些阻塞队列都实现了 BlockingQueue 接口。
以下是 BlockingQueue 接口的主要方法:
方法 | 描述 |
|---|---|
void put(E e) | 将元素插入队列的尾部,如果队列已满,则阻塞等待,直到有空间可用 |
E take() | 从队列头部取出并移除元素,如果队列为空,则阻塞等待,直到有元素可用 |
E poll(long timeout, TimeUnit unit) | 从队列头部取出并移除元素,如果队列为空,则等待最多指定的时间,如果在指定时间内没有元素可用,则返回 null |
E poll() | 从队列头部取出并移除元素,如果队列为空,则立即返回 null |
E peek() | 查看队列头部的元素,但不移除它,如果队列为空,则返回 null |
int remainingCapacity() | 返回队列剩余的容量,即还可以插入多少元素 |
boolean offer(E e, long timeout, TimeUnit unit) | 将元素插入队列的尾部,如果队列已满,则等待最多指定的时间,如果在指定时间内没有空间可用,则返回 false |
boolean offer(E e) | 将元素插入队列的尾部,如果队列已满,则立即返回 false |
int drainTo(Collection<? super E> c) | 将队列中的所有元素转移到指定的集合中,返回转移的元素数量 |
int drainTo(Collection<? super E> c, int maxElements) | 将队列中的最多 maxElements 个元素转移到指定的集合中,返回转移的元素数量 |
💥注意事项:
put() 和 take() 有阻塞等待的效果,其它方法比如 offer()、add()、poll() 等是不带阻塞功能的,所以一般只用 put() 和 take() 即可。BlockingQueue 是一个接口,创建对象时候需要用以下常见的阻塞队列实现类:队列类型 | 底层结构 | 有界性 | 处理优先级 | 线程安全机制 | 适用场景 |
|---|---|---|---|---|---|
ArrayBlockingQueue | 数组(大小固定) | 有界(必须指定) | FIFO | ReentrantLock | 严格控制任务数量、防止 OOM,适合内存敏感系统(如任务缓冲) |
LinkedBlockingQueue | 链表 | 有界或无界 | FIFO | 插入与移除锁分离(putLock / takeLock) | 任务量大但可控时可用无界; 希望高并发性能但需限制任务量时选用有界 |
SynchronousQueue | 无(容量为 0) | 无存储 | N/A | ReentrantLock | 适合高并发短任务、任务直接移交等场景 |
PriorityBlockingQueue | 堆结构 | 无界 | 优先级 | ReentrantLock | 任务需按优先级处理,如支付系统处理高金额、实时任务优先执行 |
DelayQueue | 堆结构 | 无界 | 延迟 + 优先级 | ReentrantLock | 定时任务调度,如订单超时取消、缓存过期处理 |
💥注意事项:
while 判断,而不能用 if 判断。因为比如队列为满的时候被唤醒了,也不一定队列就不满,有可能在唤醒期间有其它线程又插入了数据,此时就会出现问题!public class MyBlockingQueue {
// 使用循环队列模拟阻塞队列
private String[] arr;
private int head = 0;
private int tail = 0;
private int size; // 有效元素个数
private static final Object lock = new Object();
public MyBlockingQueue(int capacity) {
arr = new String[capacity];
}
public void put(String value) throws InterruptedException {
synchronized (lock) {
// 用 while 来判断队列是否满,看看是否要阻塞
while(size >= arr.length) {
lock.wait();
}
// 插入数据
arr[tail] = value;
tail = (tail + 1) % arr.length;
size++;
// 唤醒消费者
lock.notify();
}
}
public String take() throws InterruptedException {
synchronized (lock) {
// 用 while 来判断队列是否空,看看是否要阻塞
while(size == 0) {
lock.wait();
}
// 拿到数据
String value = arr[head];
head = (head + 1) % arr.length;
size--;
// 唤醒生产者
lock.notify();
return value;
}
}
}原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。