sleep()和yield()方法,不会释放锁,而wait()方法会释放当前线程的锁,所以,wait()方法必须是在同步代码块中调用。
-
应用场景
多个线程合作执行某项任务的时候,有线程A和线程B,它们使用同一对象锁,同一时间它们只有其中之一可以运行,另外一个线程处于等待状态。如下事件图表所示
- 线程A和线程B使用同一个把锁
- 线程A工作时,线程B等待,线程B工作时,线程A等待
- 在4点,线程A调用notify()唤醒线程B,并调用wait()方法,释放锁,任务挂起,这时线程B获取锁继续执行
- 在11点, 线程B调用notify()唤醒线程A, 并调用wait()方法,释放锁,任务挂起,这时线程A获取锁继续执行
使用wait()将当前任务挂起,让出CPU资源,比起使用while循环等待符合条件的时机,显然大大节省了CPU的开销。
-
wait()使用方式
wait方法,必须写在同步代码块内,因为底层会使用当前对象的锁。Object.wait()方法的注释中,建议用户在while循环中使用wait,有以下几点原因:
- 处于等待中的线程A和线程B被线程C的notifyAll同时唤醒,而线程B还不满足继续执行的条件,下次循环再次挂起;
- 因为处于waiting状态中的线程,有可能被虚假唤醒(spurious wakeup),这种唤醒不是通过notify或者interrupt等常规方式,因此,需要继续恢复到wait状态,所以写在while循环中,尽管虚假唤醒发生的概率非 常低。
1 synchronized (obj) { 2 while (<condition does not hold>) 3 obj.wait(); 4 // Perform action appropriate to condition 5 }
唤醒wait的线程的方法是notify和notifyAll,notify, 两者的区别是,notifyAll唤醒所有等待当前对象锁的线程,notify随机唤醒一个等待当前对象锁的线程。一般而言,使用notifyAll,在wait和while循环配合来决定哪个线程继续执行。
下面是一个例子,出自《thinking in java》,该程序是给一辆汽车反复打蜡wax、抛光buff,开启两个线程,一个打蜡,一个抛光,顺序是先打蜡再抛光,每次工作过程是:线程A打蜡,线程B等待抛光,线程A打蜡完成后唤醒线程B,线程B开始抛光,线程A等待线程B抛光,线程B抛光完成后唤醒线程A开始打蜡。。。。如此循环5秒钟,由shutdownNow中断所有线程.
1 class Car { 2 private boolean waxOn = false; 3 public synchronized void waxed() { 4 waxOn = true; // Read to buff 5 notifyAll(); 6 } 7 8 public synchronized void buffed() { 9 waxOn = false; // Read for another coat of wax 10 notifyAll(); 11 } 12 13 public synchronized void waitForWaxing() throws InterruptedException { 14 while (!waxOn) { 15 wait(); 16 } 17 } 18 19 public synchronized void waitForBuffing() throws InterruptedException { 20 while (waxOn) { 21 wait(); 22 } 23 } 24 } 25 26 class WaxOn implements Runnable { 27 private Car car; 28 29 public WaxOn(Car car) { 30 this.car = car; 31 } 32 33 @Override 34 public void run() { 35 try { 36 while (!Thread.interrupted()) { 37 System.out.println("Wax On! "); 38 TimeUnit.MILLISECONDS.sleep(200); 39 car.waxed(); 40 car.waitForBuffing(); // buffing完了才能下一次wax 41 } 42 } catch (InterruptedException e) { 43 System.out.println("Exiting via interrupt"); 44 } 45 System.out.println("Ending Wax On task!"); 46 } 47 } 48 49 class WaxOff implements Runnable { 50 private Car car; 51 52 public WaxOff(Car car) { 53 this.car = car; 54 } 55 56 @Override 57 public void run() { 58 try { 59 while (!Thread.interrupted()) { 60 car.waitForWaxing(); // wax完后才能buff 61 System.out.println("Wax Off! "); 62 TimeUnit.MICROSECONDS.sleep(200); 63 car.buffed(); 64 } 65 } catch (InterruptedException e) { 66 System.out.println("Exiting via interrupt"); 67 } 68 System.out.println("Ending Wax Off task"); 69 } 70 } 71 72 73 public class WaxOMatic { 74 public static void main(String[] args) throws InterruptedException { 75 Car car = new Car(); 76 ExecutorService exec = Executors.newCachedThreadPool(); 77 exec.execute(new WaxOn(car)); 78 exec.execute(new WaxOff(car)); 79 TimeUnit.SECONDS.sleep(5); 80 exec.shutdownNow(); 81 } 82 }
-
防止死锁发生
如下这种情况,如果线程A运行到了point1, 此时CPU调动切换到线程B,线程B进入上面的代码块,执行了notify,而此时线程A还未进入同步代码块,这时候就错过一次notify信号了,这时候进入同步代码块,就可能发生死锁永远无法被唤醒了。
synchronized (monitor) { // action monitor.notify(); } while (<condition>) { // point1 synchronized (monitor) { monitor.wait(); } }
如果要防止死锁,应该将while写在同步代码块之内
synchronized (monitor) { // action monitor.notify(); } synchronized (monitor) { while (<condition>) { monitor.wait(); } }