线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体。线程间通信就是成为整体的必用方案之一。
一、等待/通知机制
等待通知在现实中处处可见,比如就餐时,初始做好饭之前,服务员一直在等待,而饭一旦做好,通知服务员上餐,这就是等待/通知。
等待通知的实现使用wait方法和notify方法实现。
wait方法的作用使当前执行代码的线程等待,知道接到通知或被中断为止。在调用wait方法之前,线程必须获得该对象的对象级锁,即wait方法只能在同步方法或者同步代码块中调用。执行wait方法后,线程释放对象锁。使得其他等待对象锁的线程获得该对象锁。
notify方法也要在同步方法或同步代码块中调用,即在调用notify之前,线程也必须获得该对象的对象锁。当执行notify方法后,当前线程不会立即释放对象锁,而是要等到notify方法的线程将程序执行完,也就是退出synchornized代码块后,当前线程才会释放对象锁,而呈wait状态的线程才可以获取该对象锁。
class FanXin{ public static void main(String[] args) throws InterruptedException { Print p = new Print(); Thread A = new Thread(){ @Override public void run() { p.f1(); } }; A.setName("线程A"); A.start(); Thread.sleep(3000); Thread B = new Thread(){ @Override public void run() { p.f2(); } }; B.setName("线程B"); B.start(); } } class Print{ public void f1(){ try { synchronized (this){ System.out.println(Thread.currentThread().getName()+"开始"); this.wait(); System.out.println(Thread.currentThread().getName()+"结束"); } }catch (Exception e){ e.printStackTrace(); } } public void f2(){ try { synchronized (this){ System.out.println(Thread.currentThread().getName()+"开始"); this.notify(); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"结束"); } }catch (Exception e){ e.printStackTrace(); } } }如上代码,线程A执行f1方法,线程B执行f2方法,当线程A执行到wait方法时,其释放对象锁,并再次等待该对象的通知,所以它只会打印一行,如下图所示
当3秒过后,线程B启动,执行完notify方法后。刚才也说过了,线程执行notify方法后,不会立即释放对象锁,而是等线程退出synchronized代码块后才释放锁。便有了如下结果
可见,线程B在休眠期间,线程A并没有获得对象锁。当线程B退出synchronized代码块后,线程A获得该对象锁,继续执行未完的方法。结果如下
刚才的例子只是唤醒一个线程,如果还有其他线程在等待,且唤醒的线程执行条件不足,而没有执行,现在所有的线程都在等待锁的释放,这个时候如果我们唤醒所有线程,让他们再次去竞争锁,这样发生死锁的概率就降低了。
notifyAll方法将所有线程都会唤醒,让他们重新去竞争锁。
我们将上面的代码做一点修改。修改后的代码如下
class FanXin{ public static void main(String[] args) throws InterruptedException { Print p = new Print(); Thread A = new Thread(){ @Override public void run() { p.f1(); } }; A.setName("线程A"); Thread B = new Thread(){ @Override public void run() { p.f2(); } }; B.setName("线程B"); Thread C = new Thread(){ @Override public void run() { p.f3(); } }; C.setName("线程C"); C.start(); B.start(); A.start(); } } class Print{ private int i = 1; public void f1(){ try { synchronized (this){ if (i!=1) this.wait(); System.out.println(Thread.currentThread().getName()+"开始"); System.out.println(Thread.currentThread().getName()+"结束"); this.notify(); i=2; } }catch (Exception e){ e.printStackTrace(); } } public void f2(){ try { synchronized (this){ if (i!=2) this.wait(); System.out.println(Thread.currentThread().getName()+"开始"); System.out.println(Thread.currentThread().getName()+"结束"); this.notify(); i=3; } }catch (Exception e){ e.printStackTrace(); } } public void f3(){ try { synchronized (this){ if (i!=3) this.wait(); System.out.println(Thread.currentThread().getName()+"开始"); System.out.println(Thread.currentThread().getName()+"结束"); this.notify(); i=1; } }catch (Exception e){ e.printStackTrace(); } } }可见,有三个线程,线程A对应执行f1,线程B执行f2,线程C执行f3。问题出现了,线程C先抢到CPU,因为条件不满足,所以线程C等待,此时线程A得到CPU资源执行完f1,而notify是随即唤醒一个线程,假如,唤醒了线程C,而线程C也执行完了f3,此时
i的值为1,线程B始终得到不运行,出现了死锁,如下图所示
要解决这个问题的死锁问题,线程条盘判断入手,因为if判断只有一次,如果这次条件不符合,线程会等待,但是当被唤醒之后,这个条件就没有用处了,我们就不确定是线程从等待处开始执行,还是从判断条件执行,所以我们将if改为while,让他被唤醒时也要判断线程是否满足条件。并且我们要唤醒更多的线程来重新竞争锁,这样可以避免上面死锁的问题。代码修改如下
class FanXin{ public static void main(String[] args) throws InterruptedException { Print p = new Print(); Thread A = new Thread(){ @Override public void run() { p.f1(); } }; A.setName("线程A"); Thread B = new Thread(){ @Override public void run() { p.f2(); } }; B.setName("线程B"); Thread C = new Thread(){ @Override public void run() { p.f3(); } }; C.setName("线程C"); C.start(); B.start(); A.start(); } } class Print{ private int i = 1; public void f1(){ try { synchronized (this){ while (i != 1) this.wait(); System.out.println(Thread.currentThread().getName() + "开始"); System.out.println(Thread.currentThread().getName() + "结束"); this.notifyAll(); i = 2; } }catch (Exception e){ e.printStackTrace(); } } public void f2(){ try { synchronized (this) { while (i != 2) this.wait(); System.out.println(Thread.currentThread().getName() + "开始"); System.out.println(Thread.currentThread().getName() + "结束"); this.notifyAll(); i = 3; } }catch (Exception e){ e.printStackTrace(); } } public void f3(){ try { synchronized (this){ while (i != 3) this.wait(); System.out.println(Thread.currentThread().getName() + "开始"); System.out.println(Thread.currentThread().getName() + "结束"); this.notifyAll(); i = 1; } }catch (Exception e){ e.printStackTrace(); } } }这样就可以避免死锁。