线程间通信是指多个线程之间通过某种机制进行协调和交互,例如:线程等待和通知机制就是线程通讯的主要手段之一。
在 Java 中有以下三种实现线程等待的手段 :
- Object 类提供的 wait(),notify() 和 notifyAll() 方法;
- Condition 类下的 await(),signal() 和 signalAll() 方法;
- LockSupport 类下的 park() 和 unpark() 方法。
第一种方式的代码示例 :
Object lock = new Object();
new Thread(() -> {
synchronized (lock) {
try {
System.out.println("线程1 -> 进入等待");
lock.wait();
System.out.println("线程1 -> 继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1 -> 执行完成");
}
}).start();
Thread.sleep(1000);
synchronized (lock) {
// 唤醒线程
System.out.println("执行 notifyAll()");
lock.notifyAll();
}
第二种方式的代码示例 :
// 创建 Condition 对象 (lock 可创建多个 condition 对象)
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
// 加锁
lock.lock();
try {
// 一个线程中执行 await()
condition.await();
// 另一个线程中执行 signal()
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
上述两种线程间通信的方式其实是差不多的,只是 Condition 类它可以创建出多个对象。那为什么有了 Object 类的 wait 和 notify 的方式,还需要 condition 来干嘛呢 ?
答 :因为 Object 类的 wait 和 notify 只适用于一个任务队列,而 Condition 类的 await 和 signal 适用于多个任务队列,在多个任务队列的情况下,使用 Object 类的 wait 和 notify 可能会存在线程饿死的问题。
比如以上这种生产者消费者模型,当生产者,消费者(阻塞式的)都有多个的时候,并且此时任务队列里面没有任务了,所以消费者就会进入休眠状态,此时生产者需要做两件事情 :
- 将任务推送到任务队列
- 唤醒线程
【问题所在】
① 此时如果使用 Object 类提供的 wait 和 notify,而唤醒线程是存在两种可能的:
1)唤醒了消费者
2)唤醒了生产者
如果是唤醒了生产者,那就出问题了,当生产者这边代码执行完了就结束了,消费者这边永远不会去消费队列里的任务了,这就会导致线程饥饿问题。
② 而 Condition 类因为可以被创建多个,所以可以使用两个 Condition 对象,一个指定唤醒生产者,一个指定唤醒消费者,这样就不会出现线程饥饿了。
【结论】
所以 Condition 类的 await 和 signal 是对 Object 类的 wait 和 notify 的一个补充,它解决了 Object 类种分组不明确的问题。
第三种方式的代码示例:
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
LockSupport.park();
System.out.println("线程1被唤醒");
},"线程1");
t1.start();
Thread t2 = new Thread(() -> {
LockSupport.park();
System.out.println("线程2被唤醒");
},"线程2");
t2.start();
Thread t3 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("唤醒线程2");
LockSupport.unpark(t2);
},"线程3");
t3.start();
}
LockSupport 类又是对 Condition 类的一个补充,它可以指定唤醒某一个线程,它解决了前两种方式不能随机指定唤醒线程的问题。
具体使用哪一种,还是要根据业务来进行选择~