本文共 6222 字,大约阅读时间需要 20 分钟。
等待/通知机制的实现 1. 概述 * 方法wait()的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法, 该方法用来将当前线程置入"预执行队列"中,并且在wait()所在的代码行处停止执行, 直到接到通知或被中断为止。在调用wait()之前,线程必须获得该对象的对象级别锁, 即只能在同步方法或同步块中调用wait()方法。在执行wait()方法后,当前对象释放锁。 * 方法notify()也要在同步方法或者同步块中调用,即在调用前,线程也必须获得该对象的对象 级别锁。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由 线程规划器随机挑选出其中一个呈wait状态的线程,对其发出通知notify,并使它等待获取该对象 的对象锁。 注意:在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能 马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized 代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁。 * 总结 wait使线程停止运行,而notify使停止的线程继续运行。 2. notify()方法后当前线程还是会继续执行完: eg: public class MyList { private static List<String> list=new ArrayList<>(); public static void add(){ list.add("anyString"); } public static int size(){ return list.size(); } } public class ThreadA extends Thread{ private Object lock; public ThreadA(Object lock){ super(); this.lock=lock; } @Override public void run() { try{ synchronized (lock){ if(MyList.size()!=5){ System.out.println("wait begin "+System.currentTimeMillis()); lock.wait(); System.out.println("wait end "+System.currentTimeMillis()); } } }catch (InterruptedException e){ e.printStackTrace(); } } } public class ThreadB extends Thread{ private Object lock; public ThreadB(Object lock){ super(); this.lock=lock; } @Override public void run() { try{ synchronized (lock){ for(int i=0;i<10;i++){ MyList.add(); if(MyList.size()==5){ lock.notify(); System.out.println("已发出通知!"); } System.out.println("添加了"+(i+1)+"个元素!"); Thread.sleep(1000); } } }catch (InterruptedException e){ e.printStackTrace(); } } } public class Run { public static void main(String[] args){ try{ Object lock=new Object(); ThreadA a=new ThreadA(lock); a.start(); Thread.sleep(50); ThreadB b=new ThreadB(lock); b.start(); }catch (InterruptedException e){ e.printStackTrace(); } } } 结果: wait begin 1503146432420 添加了1个元素! 添加了2个元素! 添加了3个元素! 添加了4个元素! 已发出通知! 添加了5个元素! 添加了6个元素! 添加了7个元素! 添加了8个元素! 添加了9个元素! 添加了10个元素! wait end 1503146442540 3. 方法wait()锁释放与notify()锁不释放 wait()方法执行到时,该线程就会释放锁,notify()方法执行时,线程不会释放锁,并且代码会执行完毕,执行完毕后才释放锁。 4. 当interrupt方法遇到wait方法 当线程wait()状态时,调用线程对象的interrupt()方法会出现InterruptedException异常。 eg: public class Service { public void testMethod(Object lock){ try{ synchronized (lock){ System.out.println("begin wait()"); lock.wait(); System.out.println(" end wait()"); } }catch (InterruptedException e){ e.printStackTrace(); System.out.println("出现异常了,因为呈wait状态的线程被interrupt了!"); } } } public class ThreadA extends Thread { private Object lock; public ThreadA(Object lock){ super(); this.lock=lock; } @Override public void run() { Service service=new Service(); service.testMethod(lock); } } public class Test { public static void main(String[] args){ try{ Object lock=new Object(); ThreadA a=new ThreadA(lock); a.start(); Thread.sleep(5000); a.interrupt(); }catch (InterruptedException e){ e.printStackTrace(); } } } 效果: begin wait() 出现异常了,因为呈wait状态的线程被interrupt了! java.lang.InterruptedException at java.lang.Object.wait(Native Method) at java.lang.Object.wait(Object.java:502) at org.fkit.six.Service.testMethod(Service.java:11) at org.fkit.six.ThreadA.run(ThreadA.java:16) * 从上可以看出: * 在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放。 * 在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程 会释放对象锁,而此线程对象会进入线程等待池中,等待被唤醒。 5. 只通知一个线程 调用方法notify()一次只随机通知一个线程进行唤醒 6. 唤醒所有线程 使用notifyAll()方法可以唤醒所有该锁上的线程 7. 方法wait(long)的使用 带一个参数的wait(long)方法的功能是等待某一时间内是否有线程对锁进行唤醒, 如果超过这个时间则自动唤醒。 8. 等待wait的条件发生变化(难点哦) * 在使用wait/notify模式时,还需要注意另外一种情况,也就是wait等待的条件发生了变化, 也容易造成程序逻辑的混乱。 eg: public class ValueObject { public static List<String> list=new ArrayList<>(); } public class Add { private String lock; public Add(String lock){ super(); this.lock=lock; } public void add(){ synchronized (lock){ ValueObject.list.add("anyString"); lock.notifyAll(); } } } public class Subtract { private String lock; public Subtract(String lock){ super(); this.lock=lock; } public void subtract(){ try{ synchronized (lock){ if(ValueObject.list.size()==0){ System.out.println("wait begin ThreadName="+Thread.currentThread().getName()); lock.wait(); System.out.println("wait end ThreadName="+Thread.currentThread().getName()); } ValueObject.list.remove(0); System.out.println("list size="+ValueObject.list.size()); } }catch (InterruptedException e){ e.printStackTrace(); } } } public class ThreadSubtract extends Thread{ private Subtract r; public ThreadSubtract(Subtract r){ super(); this.r=r; } @Override public void run() { r.subtract(); } } public class ThreadAdd extends Thread { private Add p; public ThreadAdd(Add p){ super(); this.p=p; } @Override public void run() { p.add(); } } public class Run { public static void main(String[] args) throws InterruptedException{ String lock=new String(""); Add add=new Add(lock); Subtract subtract=new Subtract(lock); ThreadSubtract subtract1Thread=new ThreadSubtract(subtract); subtract1Thread.setName("subtract1Thread"); subtract1Thread.start(); ThreadSubtract subtract2Thread=new ThreadSubtract(subtract); subtract2Thread.setName("subtract2Thread"); subtract2Thread.start(); Thread.sleep(1000); ThreadAdd addThread=new ThreadAdd(add); addThread.setName("addThread"); addThread.start(); } } 效果: wait begin ThreadName=subtract1Thread wait begin ThreadName=subtract2Thread wait end ThreadName=subtract2Thread Exception in thread "subtract1Thread" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 list size=0 at java.util.ArrayList.rangeCheck(ArrayList.java:653) wait end ThreadName=subtract1Thread at java.util.ArrayList.remove(ArrayList.java:492) at org.fkit.seven.Subtract.subtract(Subtract.java:20) at org.fkit.seven.ThreadSubtract.run(ThreadSubtract.java:15) 分析:因为我们有两个线程在等待删除,而插入只执行了一次,却唤醒了两个线程,去删除的时候当然会出现错误, 做如下修改就可以解决问题 public class Subtract { private String lock; public Subtract(String lock){ super(); this.lock=lock; } public void subtract(){ try{ synchronized (lock){ while(ValueObject.list.size()==0){ System.out.println("wait begin ThreadName="+Thread.currentThread().getName()); lock.wait(); System.out.println("wait end ThreadName="+Thread.currentThread().getName()); } ValueObject.list.remove(0); System.out.println("list size="+ValueObject.list.size()); } }catch (InterruptedException e){ e.printStackTrace(); } } } 分析:现在来考虑这个问题,当我们线程被唤醒成功的时候,不是立即去执行删除操作,而是回头再去判断一次,因为线程 被同步了,顺序执行,当第二个线程执行到的时候就已经为0了,重新进入等待,而不会去执行删除,就避免了这种错误。转载地址:http://ymjqi.baihongyu.com/