线程是操作系统中独立的个体,如果不经过处理就不能成为一个整体。使线程进行通信后,系统之间的交互性会更强大,在大大提高CPU利用率的同时还能够对个线程任务在处理过程中进行有效的把控和监督。
等待/通知机制
首先看一下一个不用等待/通知机制实现线程间通信的例子,使用sleep结合while(true)死循环来实现.
public class MyList { private List list = new ArrayList(); public void add(){ list.add(" "); } public int size() { return list.size(); } } public class ThreadA extends Thread { MyList myList; public ThreadA(MyList list){ this.myList= list; } public void run(){ for(int i= 0; i < 10; i ++){ myList.add(); ThreadA.sleep(1000); } } } public class ThreadB extends Thread { MyList myList; public ThreadB(MyList list){ this.myList= list; } public void run(){ while(true){ if(myList.size() == 5){ throw new InterruptedException(); } } } } public class Test { public static void main(String [] args){ MyList list = new MyList(); ThreadA a = new ThreadA(list); ThreadB b = new ThreadB(list); a.start(); b.start(); } } 运行结果:在size等于5的时候,ThreadB退出
ThreadB中不停的通过while语句轮询机制来检测某一个条件,这样极大的浪费了CPU资源。
由wait/notify方法实现的等待/通知机制
①wait方法的作用是使当前执行代码的线程进行等待,wait方法时Object类的方法。执行wait方法即将当前线程置入“预执行队列”中,并且在wait所在的代码处停止执行,直到接到通知或被中断为止。
在调用wait之前,线程必须获得该对象的对象级别锁,即只能在同步方法或同步代码块中调用wait方法,否则会抛出IllegalMonitorStateException异常,是RuntimeException的子类,不需要进行try...catch进行捕捉。
在从wait方法返回前,线程要与其他线程竞争重新获得锁。
②notify方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出一个呈wait状态的线程,对其发出通知notify,并使它等待获取该对象的对象锁。
在调用notify方法之前线程也必须获取该对象的对象级别锁,否则会抛出异常。
在执行notify方法之后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能马上获取该对象锁,要等到执行notify方法的线程将程序执行完,即退出synchronized代码块后,当前线程才会释放锁,而呈wait状态的线程才可以获得该对象锁。
当第一个获得了该线程锁的wait线程执行完毕后,它会释放该线程锁,但此时该对象若没有再次发出通知的话,则即便该对象已经空闲,其他线程由于没有得到该对象的通知,还会继续阻塞在wait状态,知道这个对象发出通知。
③下面用wait/notify来实现前面的例子
public class MyList { private static List list = new ArrayList(); public static void add(){ list.add(" "); } public static int size() { return list.size(); } } public class ThreadA extends Thread { Object lock; public ThreadA(Object lock){ this.lock = lock; } public void run(){ synchronized(lock){ for(int i= 0; i < 10; i ++){ MyList.add(); if(MyList.size() == 5){ lock.notify(); } } } } public class ThreadB extends Thread { Object lock; public ThreadB(Object lock){ this.lock = lock; } public void run(){ synchronized(lock){ if(MyList.size() != 5){ lock.wait(); } } } public class Test { public static void main(String [] args){ Object lock = new Object(); ThreadA a = new ThreadA(lock); ThreadB b = new ThreadB(lock); b.start(); Thread.sleep(50); a.start(); } } 运行结果:在size等于5的时候,ThreadB接收到通知。
分析:线程b首先执行。因为size!= 5,所以线程b进入了wait状态,线程a开始执行,知道size==5时,发出通知,线程b进入等待状态,但线程a不会立即释放锁会一直等到size == 10的时候,释放锁。这时线程b恢复运行。
④当方法wait()执行完后,锁被自动释放,但执行完notify()方法后,锁却不自动释放。
⑤当线程呈wait状态时,调用线程对象的interrupt方法会出现InterruptedException异常。
⑥调用notify方法一次只随机通知一个线程进行唤醒。为了唤醒全部线程,可以使用notifyAll()方法。
⑦wait(long)方法。是等待一段时间内是否有线程对锁进行唤醒,如果超过这个时间则自动唤醒。
⑧若通知过早,比如notify方法在wait方法之前执行,则wait线程不会被唤醒。
⑨若wait等待的条件发生了变化,则很容易造成程序逻辑的混乱。
public class Add { private String lock; public Add(String lock){ this.lock = lock; } public void add(){ synchronized(lock){ ValueObject.list.add("str"); lock.notifyAll(); } } } public class Subtract { private String lock; public Subtract(String lock){ this.lock = lock; } public void subtract(){ synchronized(lock){ if(ValueObject.list.size() == 0){ lock.wait(); } ValueObject.list.remove(0); } } } public class ValueObject { public static List list = new ArrayList(); } public class ThreadAdd extends Thread { private Add add; public Thread(Add add){ this.add = add; } public void run(){ add.add(); } } public class ThreadSubtract extends Thread { private Subtract s; public Thread(Subtract sub){ this.s = sub; } public void run(){ s.subtract(); } } public class Run { public static void main(String [] args){ String lock =new String(""); Add add = new Add(lock); Subtract sub = new Subtract(lock); ThreadSubtract s1 = new ThreadSubtract(sub); ThreadSubtract s2 = new ThreadSubtract(sub); ThreadAdd a = new ThreadAdd(add); s1.start(); s2.start(); Thread.sleep(1000); a.start(); } } 运行结果:出现异常IndexOutOfBoundException。
分析:线程s1和s2先执行,这时size == 0,他们都进入等待状态,然后a线程执行一次add操作后,执行notifyAll唤醒全部线程。这时线程s1和s2都要执行remove操作,所以出现异常。如何解决呢?
将Subtract中的if改为while就不会再出现异常了 public class Subtract { private String lock; public Subtract(String lock){ this.lock = lock; } public void subtract(){ synchronized(lock){ while(ValueObject.list.size() == 0){ lock.wait(); } ValueObject.list.remove(0); } } }
分析:线程s1和s2都进入了wait状态后,a线程执行add操作bing唤醒全部线程。s1线程先抢到了锁,这时s1线程要进入下一次while循环的条件判断操作,size!= 0,所以,退出while循环,执行remove操作,s1线程释放锁,s2线程开始恢复执行,在进行while循环的条件判断时发现 size == 0,于是又进入了while循环内部,s2线程有进入了wait状态。所以不会有异常发生。