一. 等待通知机制的实现#
方法名 | 作用 |
wait() | 执行当前代码的线程等待 |
wait(long timeout) | timeout时间内若没有其他线程唤醒,也会醒过来 |
wait(long timeout, int nanos) | 超出timeout和额外的时间nanos,没有被其他线程唤醒,也会醒过来 |
方法名 | 作用 |
notify() | 随机唤醒一条在等待队列中想去访问同一共享变量的线程 |
void notifyAll() | 唤醒在此对象监视器上等待的所有线程 |
wait()可以使当前线程停下来,等待某个条件发生变化,并且这个条件超出了当前方法的控制范围,可以实现和自旋一样的效果,但是呢,自旋会比较占用CPU的资源
实例代码:
public class demo1 { private static List list = new ArrayList(); public void add(){ list.add("string"); } public int size(){ return list.size(); } }
public class demo11 { public static void main(String[] args) { demo1 demo1 = new demo1(); Object o = new Object(); new Thread(()->{ synchronized (o){ if(demo1.size()!=5){ try { System.out.println("开始等待..."+ System.currentTimeMillis()); o.wait(); System.out.println("等待结束.."+System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); new Thread(()->{ synchronized (o){ for(int i=0;i<10;i++){ demo1.add(); if (demo1.size()==5){ System.out.println("发出notofy通知..."); o.notify(); } System.out.println("已经添加了"+i+"个元素"); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); }
运行结果:
开始等待...1549259002081
已经添加了0个元素
已经添加了1个元素
已经添加了2个元素
已经添加了3个元素
发出notofy通知...
已经添加了4个元素
已经添加了5个元素
已经添加了6个元素
已经添加了7个元素
已经添加了8个元素
已经添加了9个元素
等待结束..1549259005086
wait() & notify() & notifyAll()#
wait()总结:
- wait()方法是Object的方法,作用是让执行当前代码的线程进行等待,(置入到预执行队列),并且会记住当前线程执行到了哪一行代码,当现场被唤醒后,继续从记住的那行代码往下执行
- wait()方法使用的前提,线程必须获取到对象级别的锁,这也就意味着,wait()必须在synchronized同步方法,或者同步代码块中才能执行
- 若没有获取到对象锁,抛出异常IllegalMonitorStateExeception
- 当调用wait()方法后,会立刻释放当前的对象锁
notify()总结:
- notify()同样是Object的方法,调用此方法的效果是:随机的在唤醒一个等待队列中等待访问同一个共享资源的一个线程
- notifyAll()唤醒所有,(此时,优先级更高的那个线程,有更大几率先执行,但是也一8I吗确定)
- notify()使用的前提同样也是线程首先获取到对象级别的锁
- 调用notify()后,不会立即释放锁,而是继续执行notify()所在的方法,直到此同步方法执行结束后,才会释放对象锁,这也就意味着,notify()之后,wait()状态的线程不会立即被唤醒.
notifyAll()的调用,必须提前获取到锁,而wait()一经调用,立即释放锁,两者不会冲突
二 解决过早通知问题#
- 试想,如果通知过早,那么就会打乱正常的逻辑,wait()也就没必要执行了,因为它永远都醒不了
public class demo2 { String lock = new String(""); boolean tag = false; private Runnable runnableA = new Runnable(){ @Override public void run() { synchronized (lock){ try { while(tag==false) { System.out.println("runnableA bagin wait..."); lock.wait(); System.out.println("RunnableA wait end..."); } } catch (InterruptedException e) { e.printStackTrace(); } } } }; private Runnable runnableB = new Runnable(){ @Override public void run() { synchronized (lock){ System.out.println("runnableB bagin notify..."); lock.notify(); tag=true; System.out.println("RunnableB notify end..."); System.out.println("不满足条件不唤醒..."); } } }; public static void main(String[] args) { demo2 demo2 = new demo2(); new Thread(demo2.runnableB).start(); new Thread(demo2.runnableA).start(); } }
执行结果:
runnableB bagin notify... RunnableB notify end... 不满足条件不唤醒...
添加了一个判断的条件,实现,若现进行了唤醒,那么不执行wait()
更换两个线程的启动顺序
运行结果:
E:\JavaJDK\bin\java.exe runnableA bagin wait... runnableB bagin notify... RunnableB notify end... 不满足条件不唤醒... RunnableA wait end...
三. 等待wait()的条件发生变化与解决#
运行下面的代码:
public class demo33 { private String lock; public demo33(String lock){ this.lock =lock; } private List list = new ArrayList(); public void add(){ synchronized (lock){ list.add("hello"); System.out.println(Thread.currentThread().getName()+"add hello 然后唤醒所有wait()线程"); //唤醒所有 lock.notifyAll(); } } public void subtract(){ synchronized (lock){ if(list.size()==0){ try { System.out.println(Thread.currentThread().getName()+"开始等待"); lock.wait(); System.out.println(Thread.currentThread().getName() +"等待结束"); } catch (InterruptedException e) { e.printStackTrace(); } } list.remove(0); } } } public static void main(String[] args) { demo33 demo33 = new demo33("123"); // 第一条等待的线程 new Thread(()->{ demo33.subtract(); }).start(); //第二条等待的线程 new Thread(()->{ demo33.subtract(); }).start(); //唤醒所有 new Thread(()->{ demo33.add(); }).start(); }
运行结果:
Thread-0开始等待 Thread-1开始等待 Thread-2add hello 然后唤醒所有wait()线程 Thread-1等待结束 Thread-0等待结束 Exception in thread "Thread-0" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 at java.util.ArrayList.rangeCheck(ArrayList.java:657)
- 抛出了异常,原始就是因为wait的条件发生了改变,前两条线程因为if(list.size()0)而wait(),紧接着,add()一个元素后,notifyAll()唤醒了所有等待中的线程,于是,那两条等待中的线程在wait()处,继续往下执行remove(0),我们知道,仅仅是添加了一个元素,第二次remove(0)的时候,是非法的,**而在我们的判断if(list.size()0){..}中判断反应不过来**于是抛出了异常
- 解决方案很简单,既然来不及判断,我们就用while()替换if()这样一来,while()会比if()多执行一次,发现条件满足,继续等待
四. 生产者消费者模式#
1 解决多生产多消费: 操作值 -- 假死现象#
/* * 多生产,多消费的假死现象 * */ public class demo4 { private List list = new ArrayList(); public void p(Object o){ synchronized (o){ while(!(list.size()==0)){ //对于生产者,list不为空,等待 System.out.println("生产者"+Thread.currentThread().getName()+"等待了..."); try { o.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 为空,生产 System.out.println("生产者"+Thread.currentThread().getName()+"生产了..."); list.add("123"); o.notify(); } } public void c(Object o){ synchronized (o){ while(list.size()==0){ //对于消费者,size==0 等待 System.out.println("消费者"+Thread.currentThread().getName()+"等待了"); try { o.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("消费者"+Thread.currentThread().getName()+"消费了"); list.remove(0); o.notify(); } } public static void main(String[] args) { demo4 demo4 = new demo4(); Object o = new Object(); ExecutorService executorService = Executors.newCachedThreadPool(); for (int i=0;i<5;i++){ executorService.execute(new Runnable() { @Override public void run() { while (true) demo4.p(o); } }); executorService.execute(new Runnable() { @Override public void run() { while (true) demo4.c(o); } }); } Thread[] threads = new Thread[2]; try { Thread.sleep(5000); System.out.println("主函数醒了"); } catch (InterruptedException e) { e.printStackTrace(); } } }
运行上面的代码结果
. . . 生产者pool-1-thread-7等待了... 消费者pool-1-thread-10消费了 消费者pool-1-thread-10等待了 消费者pool-1-thread-8等待了 消费者pool-1-thread-6等待了 主函数醒了
卡顿在最后"主函数醒了"不再往下运行,产生了假死的现象实际上还有线程依然存活,只不过是它一直等待,没人唤醒它,notify()会随机唤醒一条线程,这也就意味着,可能存在生产者唤醒的是生产者,消费者唤醒了消费者,导致双方全部等待而造成假死
- 解决方法,把notify()换成notifyAll()(它不但会唤醒同己,也会唤醒异己)
2 . 一生产与多消费--操作栈,解决wait条件改变与假死问题#
- 解决wait条件改变---使用while替换if进行判断
- 假死的原因依然是唤醒了同类---notifyAll()替换notify()