多线程编程之线程间的通信——wait and notify

来源:互联网 发布:ubuntu软件中心打不开 编辑:程序博客网 时间:2024/06/06 02:32

wait/notify基本概念

wait自动释放锁
当执行wait方法后,当前线程进入阻塞队列等待唤醒,唤醒后的线程进入就绪队列,等待cup分配资源。

当执行wait方法后,当前线程自动释放锁,进入阻塞队列。

package waitLetLockGo;public class Service {    public void testMethod(Object lock) {            try {                synchronized (lock) {                    System.out.println("start wait");                    lock.wait();                    //Thread.sleep(5000);//若将代码改成休眠,那么就成了同步了。                    System.out.println("end wait");                }            } catch (InterruptedException e) {                e.printStackTrace();            }    }}package waitLetLockGo;public class ThreadA extends Thread{    Object lock;    public ThreadA(Object lock) {        super();        this.lock=lock;    }    public void run() {        Service service=new Service();        service.testMethod(lock);    }}package waitLetLockGo;public class ThreadB extends Thread{    Object lock;    public ThreadB(Object lock) {        super();        this.lock=lock;    }    public void run() {        Service service=new Service();        service.testMethod(lock);    }}package waitLetLockGo;//线程A先获得锁,然后线程A进入阻塞队列,然后线程B获得锁,进入阻塞队列。//都没有被唤醒//结论:执行wait会释放锁。public class Run {    public static void main(String[] args) {        Object lock=new Object();        ThreadA a=new ThreadA(lock);        a.start();        ThreadB b=new ThreadB(lock);        b.start();    }}

控制台输出:

start waitstart wait

结论:执行wait会释放锁。

notify不释放锁:

package notifyNotLetLockGo;public class Service {    public void testMethod(Object lock) {        try {            synchronized (lock) {                System.out.println("begin wait() ThreadName:"+Thread.currentThread().getName());                lock.wait();                System.out.println("end wait() ThreadName:"+Thread.currentThread().getName());            }        }catch(InterruptedException e){            e.printStackTrace();        }    }    public void synNotifyMethod(Object lock) {        try {            synchronized (lock) {                System.out.println("start notify ThreadName:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());                lock.notify();                Thread.sleep(5000);                System.out.println("end notify ThreadName:"+Thread.currentThread().getName()+" time"+System.currentTimeMillis());            }        }catch(InterruptedException e) {            e.printStackTrace();        }    }}package notifyNotLetLockGo;public class ThreadA extends Thread{    Object lock;    public ThreadA(Object lock) {        super();        this.lock=lock;    }    public void run() {        Service service=new Service();        service.testMethod(lock);    }}package notifyNotLetLockGo;public class NotifyThread extends Thread{    Object lock;    public NotifyThread(Object lock) {        super();        this.lock=lock;    }    public void run() {        Service service=new Service();        service.synNotifyMethod(lock);    }}package notifyNotLetLockGo;public class synNotifyMethodThread extends Thread{    Object lock;    public synNotifyMethodThread(Object lock) {        super();        this.lock=lock;    }    public void run() {        Service service=new Service();        service.synNotifyMethod(lock);    }}package notifyNotLetLockGo;//实验目的:必须执行完notify方法所在的同步代码块后才释放锁。public class Test {    public static void main(String[] args) {        Object lock=new Object();        ThreadA a=new ThreadA(lock);        a.start();        NotifyThread notifyThread=new NotifyThread(lock);        notifyThread.start();        synNotifyMethodThread c=new synNotifyMethodThread(lock);        c.start();    }}

控制台输出:

begin wait() ThreadNameThread-0start notify ThreadName:Thread-1 time:1506777344320end notify ThreadName:Thread-1 time1506777349321end wait() ThreadName:Thread-0start notify ThreadName:Thread-2 time:1506777349321end notify ThreadName:Thread-2 time1506777354321

结论:必须执行完notify方法所在的同步synchronized代码块后才释放锁。

就像sleep(arg)方法一样,wait()方法也可以有参数,例如wait(2000),当线程调用后进入等待的状态,如果在2000毫秒内没有其他线程对其唤醒,那么等待的线程就会自动的唤醒。

package waitLong;public class MyRunnable {    static private Object lock=new Object();    static private Runnable runnable=new Runnable() {        public void run(){            try {                synchronized (lock) {                    System.out.println("begin wait time:"+System.currentTimeMillis());                    lock.wait(5000);                    System.out.println("end wait time:"+System.currentTimeMillis());                }            }catch(InterruptedException e) {                e.printStackTrace();            }        }    };    public static void main(String[] args) {        Thread t=new Thread(runnable);        t.start();    }}

控制台输出:

begin wait time:1507688368045end wait time:1507688373046

上面的例子中,在调用了wait(5000)后,并没有其他线程对其唤醒,但是我们看一下控制台可以发现,过了5s后线程自动退出等待的状态了。
当然,在等待的过程中也可以由其他线程对它唤醒。

package waitLong;//提前唤醒public class runnable {    static private Object lock=new Object();    static private Runnable runnable=new Runnable() {        public void run(){            try {                synchronized (lock) {                    System.out.println("begin wait time:"+System.currentTimeMillis());                    lock.wait(5000);                    System.out.println("end wait time:"+System.currentTimeMillis());                }            }catch(InterruptedException e) {                e.printStackTrace();            }        }    };    static private Runnable runnable2=new Runnable() {        public void run() {            synchronized (lock) {                System.out.println("begin notify time:"+System.currentTimeMillis());                lock.notify();                System.out.println("end notify time:"+System.currentTimeMillis());            }        }    };    public static void main(String[] args) throws InterruptedException {        Thread t=new Thread(runnable);        t.start();        Thread.sleep(3000);        Thread t2=new Thread(runnable2);        t2.start();    }   }

控制台输出:

begin wait time:1507688633788begin notify time:1507688636788end notify time:1507688636788end wait time:1507688636788

调用notify()方法一次只随机唤醒一个线程,如果想要唤醒多个线程就需要多次调用这个方法,当然你也可以选择调用notifyAll()(使用方法和notify方法相同)。

除了以上的一些用法,还需要注意的就是:
①避免notify过早,如果没有线程进入等待状态,这个notify通知等于未唤醒任何线程。同时当后续线程需要唤醒的时候却没有线程对它唤醒,容易造成无限等待。
解决方案:避免出现无wait状态下使用notify
②等待的条件发生变化,即:线程A等待取一个字符串,当线程B未完成将一个字符串放入一个队列中时候,线程A陷入等待,当线程B将一个字符串放入队列同时发出notifyAll通知,线程A被唤醒了,但是这个字符串却被同时在等待的线程C给截走了,所以线程A虽然被唤醒了,但它被唤醒的条件是无字符串可取,所以说是wait的条件改变了,伴随着的也就是IndexOutOfBoundsException错误。
解决方案:将wait条件控制语句if改为while。
以下就是一个错误的案例:
(如果修改,只需要将类Subtract中if(ValueObject.list.size()==0)更改为while(ValueObject.list.size()==0))

package waitOld;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();        }    }}package waitOld;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 begain thread name:"+Thread.currentThread().getName());                    lock.wait();                    System.out.println("wait end thread name:"+Thread.currentThread().getName());                }                ValueObject.list.remove(0);                System.out.println("list size="+ValueObject.list.size());            }        }catch(InterruptedException e) {            e.printStackTrace();        }    }}package waitOld;public class ThreadAdd extends Thread{    private Add p;    public ThreadAdd(Add p) {        super();        this.p=p;    }    public void run() {        p.add();    }}package waitOld;public class ThreadSubtract extends Thread{        private Subtract r;        public ThreadSubtract(Subtract r) {            super();            this.r=r;        }        public void run() {            r.subtract();        }    }package waitOld;import java.util.ArrayList;import java.util.List;public class ValueObject {    public static List list=new ArrayList();}package waitOld;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.start();    }}

生产者消费者模式

等待/通知最经典的案例就是“生产者消费者模式”了。

一生产一消费:操作值

package producterAndConsumer;//生产者public class P {    private String lock;    public P(String lock) {        super();        this.lock=lock;    }    public void  setValue() {        try {            synchronized (lock) {                if(!ValueObject.value.equals("")) {                    lock.wait();                }                String value=System.currentTimeMillis()+"_"+System.nanoTime();                System.out.println("set 的值是:"+value);                ValueObject.value=value;                lock.notify();            }        }catch(InterruptedException e){            e.printStackTrace();        }    }}package producterAndConsumer;//消费者public class C {    String lock;    public C(String lock) {        super();        this.lock=lock;    }    public void getValue() {        try {            synchronized (lock) {                if(ValueObject.value.equals("")) {                    lock.wait();                }                System.out.println("get的值是:"+ValueObject.value);                ValueObject.value="";                lock.notify();            }        }catch(InterruptedException e) {            e.printStackTrace();        }    }}package producterAndConsumer;//生产者线程public class ThreadP extends Thread {    private P p;    public ThreadP(P p) {        super();        this.p=p;    }    public void run() {        while(true) {            p.setValue();        }    }}package producterAndConsumer;//消费者线程public class ThreadC extends Thread {    private C c;    public ThreadC(C c) {        super();        this.c=c;    }    public void run() {        while(true) {            c.getValue();        }    }}package producterAndConsumer;//实体类,用于存放数据public class ValueObject {    public static String value="";}package producterAndConsumer;//测试代码public class run {    public static void main(String[] args) {        String lock=new String("");        P p=new P(lock);        C c=new C(lock);        ThreadP threadP=new ThreadP(p);        ThreadC threadC=new ThreadC(c);        threadP.start();        threadC.start();    }}

以上是一生产者一消费者,在此基础上可以设计多生产者和多消费者。但是会陷入假死状态,即所有线程都进入等待状态。
为什么会出现这样的情况?因为一个生产者线程通知是随机的,可能唤醒生产者线程(生产检测时候发现value中已经有数据了,进入等待状态),也可能唤醒消费者(我们所期望的情况),但是随着程序不断的进行,所有的线程都会进入waiting(假死)状态。
修改方法:将notify()改为notifyAll(),不仅唤醒生产者线程同时也唤醒消费者线程。

上面的例子工具类使用的是包含一个字符串的对象。接下来的例子试讲生产者将数据放入list对象中:
一生产一消费:操作栈

package producterAndConsumer1;import java.util.ArrayList;import java.util.List;//工具类public class MyStack {    private List list=new ArrayList();    synchronized public void push() {        try {            if(list.size()==1) {                this.wait();            }            list.add("anyString="+Math.random());            this.notify();            System.out.println("push="+list.size());        }catch(InterruptedException e) {            e.printStackTrace();        }    }    synchronized public String pop() {        String returnValue="";        try {            if(list.size()==0) {                System.out.println("pop操作中的"+Thread.currentThread().getName()+"线程呈wait状态");                this.wait();            }            returnValue=""+list.get(0);            list.remove(0);            this.notify();            System.out.println("pop="+list.size());        }catch(InterruptedException e) {            e.printStackTrace();        }        return returnValue;    }}package producterAndConsumer1;//生产者public class P {    private MyStack mystack;    public P(MyStack mystack) {        super();        this.mystack=mystack;    }    public void putService() {        mystack.push();    }}package producterAndConsumer1;//消费者public class C {    private MyStack mystack;    public C(MyStack mystack) {        super();        this.mystack=mystack;    }    public void popService() {        System.out.println("pop="+mystack.pop());    }}package producterAndConsumer1;//生产者线程public class ThreadP extends Thread{    private P p;    public ThreadP(P p) {        super();        this.p=p;    }    public void run() {        while(true) {            p.putService();        }    }}package producterAndConsumer1;//消费者线程public class ThreadC extends Thread{    private C c;    public ThreadC(C c) {        this.c=c;    }    public void run() {        while(true) {            c.popService();        }    }}package producterAndConsumer1;//测试方法public class run {    public static void main(String[] args) {         MyStack mystack=new MyStack();         P p=new P(mystack);         C c=new C(mystack);         ThreadP threadp=new ThreadP(p);         ThreadC threadc=new ThreadC(c);         threadp.start();         threadc.start();    }}控制台部分结果:pop=anyString=0.10952348259646205pop操作中的Thread-1线程呈wait状态push=1pop=0pop=anyString=0.3117061801875739pop操作中的Thread-1线程呈wait状态push=1pop=0pop=anyString=0.5919274328346097pop操作中的Thread-1线程呈wait状态push=1pop=0pop=anyString=0.8462987957012925pop操作中的Thread-1线程呈wait状态push=1pop=0pop=anyString=0.1697638369373462pop操作中的Thread-1线程呈wait状态

从以上的基础上改造为:一生产多消费,因为条件的更改(其他线程取走了数据,就是我们上面说过的:wait条件发生变化)导致一些线程发错误,同理我们需要将if条件控制更改为while。

若是改成多生产多消费的话:也会像出现像操作值那样假死,所以也要将notify()方法改为notifyAll()方法。

阅读全文
0 0
原创粉丝点击