一 . 概述
在前面我们使用各种方式解决了线程的安全性问题,无论是加锁和是使用无锁的方式,本质上讲都是控制线程的执行顺序.
但是前面对线程的控制顺序还只是出于粗粒度的控制,我们常常希望看到的是一个线程完成任务然后激发另外一个线程的线程任务的执行,
或者两个线程交互完成任务(生产者生产一个产品,消费者消费一个产品).
二 . 解决方式
我们使用线程的通信来完成任务,本质上讲线程通信还是在加锁的基础上增加更多的控制元素.
也就是说线程的通信的代价会更高,但是比较起线程的正确运行,这一点性能的损失还是微不足道的.
java之中的内置锁加上synchronized可以帮助我们实现原子操作,而内置锁同样可以实现等待池的功能.
下面我们实现一个生产者和消费者的模型.
public class ConsumerAndProduct { private final static Object lock = new Object(); public static void main(String[] args) { // 生产者 new Thread(() -> { for(;;) { synchronized (lock) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e1) { e1.printStackTrace(); } System.out.println("生产者生产了一个产品..."); try { //唤醒消费者 lock.notify(); //生产者休眠 lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); new Thread(() -> { for(;;) { synchronized (lock) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e1) { e1.printStackTrace(); } System.out.println("消费者消费了一个产品..."); try { //唤醒生产者 lock.notify(); //消费者休眠 lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } }
上面的例子中,生产者和消费者可以相互交替的运行,我们现在只是简单的演示一个wait()和notify()notifyAll()方法的使用.
[1] 上面介绍的方法必须在synchronized之中运行,否则会抛出异常.
[2]wait()方法会将当前的线程进入锁的等待池,直到有线程从等待池中唤醒该线程.
[3]notify()从内置锁的等待池中唤醒一个线程,而notifyAll()会唤醒所有的线程.
[4]唤醒的线程只是进入runnable状态之中,并不代表会直接运行.
三 . 线程通信
我们从上面看到线程通信需要建立在线程的同步之上的,也就是说线程的通信可以更加细粒度的完成线程的控制,
当然代价就是线程可能进入阻塞和等待队列之中,性能也就降低了.
使用内置锁完成线程的通信只是jdk提供的基本方式,在JUC之中有大量的通信方式帮助我们实现功能,这些大部分都基于无锁或者AQS完成的.