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

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 wait
start 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-0
start notify ThreadName:Thread-1 time:1506777344320
end notify ThreadName:Thread-1 time1506777349321
end wait() ThreadName:Thread-0
start notify ThreadName:Thread-2 time:1506777349321
end 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:1507688368045
end 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:1507688633788
begin notify time:1507688636788
end notify time:1507688636788
end 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.10952348259646205
pop操作中的Thread-1线程呈wait状态
push=1
pop=0
pop=anyString=0.3117061801875739
pop操作中的Thread-1线程呈wait状态
push=1
pop=0
pop=anyString=0.5919274328346097
pop操作中的Thread-1线程呈wait状态
push=1
pop=0
pop=anyString=0.8462987957012925
pop操作中的Thread-1线程呈wait状态
push=1
pop=0
pop=anyString=0.1697638369373462
pop操作中的Thread-1线程呈wait状态

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值