本文部分摘自《Java 并发编程的艺术》
volatile 和 synchronize 关键字
每个处于运行状态的线程,如果仅仅是孤立地运行,那么它产生的作用很小,如果多个线程能够相互配合完成工作,则将带来更大的价值
Java 支持多个线程同时访问一个对象或者对象的成员变量,使用 volatile 关键字可以保证被修饰变量的可见性,意味着任一线程对该变量的任何修改,其他线程都可以立即感知到
synchronize 关键字可以修饰方法或者同步块,它主要确保多个线程在同一时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性。synchronize 关键字的实现,本质是对一个对象的监视器(monitor)进行获取,而这个获取过程是排他的,也就是同一时刻只能有一个线程获取到由 synchronize 所保护对象的监视器
任何一个对象都拥有自己的监视器,任意一个线程对 Object 的访问(Object 由 synchronize 保护)的访问,首先要获得 Object 的监视器。如果获取失败,线程进入同步队列,线程状态变为 BLOCKED。当访问 Object 的前驱(获得了锁的线程)释放了锁,则该释放操作将唤醒阻塞在同步队列中的线程,使其重新尝试获取监视器
等待 - 通知机制
一个线程修改了一个对象的值,另一个线程感知到变化,然后进行相应的操作,前者是生产者,后者是消费者,这种通信方式实现了解耦,更具伸缩性。在 Java 中为了实现类似的功能,我们可以让消费者线程不断地循环检查变量是否符合预期,条件满足则退出循环,从而完成消费者的工作
while(value != desire) { Thread.sleep(1000);}doSomething();
睡眠一段时间的目的是防止过快的无效尝试,这种实现方式看似能满足需求,但存在两个问题:
难以确保及时性
如果睡眠时间太长,就难以及时发现条件已经变化
难以降低开销
如果降低睡眠时间,又会消耗更多的处理器资源
使用 Java 提供了内置的等待 - 通知机制能够很好地解决上述问题,等待 - 通知的相关方法是任意 Java 对象都具备的
方法名称
描述
notify()
通知一个在对象上等待的线程,使其从 wait() 方法返回,返回的前提是该线程获取到了对象的锁
notifyAll()
通知所有等待在该对象上的线程
wait()
调用该方法的线程进入 WAITING 状态,只有等待另外的线程通知或被中断才返回,调用此方法会释放对象的锁
wait(long)
超时等待一段时间,参数时间是毫秒
wait(long, int)
对于超时时间更细粒度的控制,可以达到纳秒
等待 - 通知机制,是指一个线程 A 调用了对象 O 的 wait() 方法进入等待状态,而另一个线程 B 调用了对象 O 的 notify() 或者 notifyAll() 方法,线程 A 收到通知后从对象 O 的 wait() 方法返回,进而执行后续操作。上述两个线程通过对象 O 来完成交互,而对象上的 wait() 和 notify/notifyAll() 的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作
下述例子中,创建两个线程 WaitThread 和 NotifyThread,前者检查 flag 值是否为 false,如果符合要求,进行后续操作,否则在 lock 上等待,后者在睡眠一段时间后对 lock 进行通知
public class WaitNotify { static boolean flag = true; static Object lock = new Object(); public static void main(String[] args) throws InterruptedException { Thread waitThread = new Thread(new Wait(), "WaitThread"); waitThread.start(); TimeUnit.SECONDS.sleep(1); Thread notifyThread = new Thread(new Notify(), "NotifyThread"); notifyThread.........