您的位置:首页 > 编程语言 > Java开发

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

2017-08-19 22:15 387 查看
等待/通知机制的实现

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){

               
d758
        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了,重新进入等待,而不会去执行删除,就避免了这种错误。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  多线程 java