多线程 等待/通知机制的实现

来源:互联网 发布:软件销售管理制度 编辑:程序博客网 时间:2024/05/29 07:01
等待/通知机制的实现
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了,重新进入等待,而不会去执行删除,就避免了这种错误。