线程间通信与协作方式之——wait-notify机制

大家好,上篇文章为大家介绍了线程间通信和协作的一些基本方式,那这篇文章就来介绍一下经典的wait-notify机制吧。

什么是wait-notify机制?

想象一下有两个线程A、B,如果业务场景中需要这两个线程交替执行任务(比如A执行完一次任务后换B执行,B执行完后再换A执行这样重复交替),之前的基本通信方式只能让线程暂停一段指定时间,Join方法也无法做到这种交替执行的要求,那怎么办呢?

别急,针对这种场景java同样为我们提供了一种经典的线程通信方式——wait-notify机制,这里涉及到下面的三个方法(关于锁的知识后文会详细讲):

wait方法:当前线程在调用wait方法会先让出当前线程持有的对象锁以便让其他线程能够获取,然后当前线程会停止执行并进入WAITING状态。直到接收到唤醒或中断信号后,当前线程才会继续尝试获取对象锁。如果此时获取对象锁成功,就能继续执行任务。

notify方法:当前线程的任务即将执行完毕并发出唤醒信号,此时只有接收到唤醒信号的线程才会尝试获取对象锁。当然此时可能获取对象锁会失败,因为notify方法不会即时释放锁,而是需要等到线程执行完毕后才会真正释放锁。

notifyAll方法:和notify方法作用相似,唯一不同的就是该方法会对当前所有在等待这个对象锁的线程发出唤醒信号。至于最终是哪个线程抢到了对象锁,就要看哪个线程比较“幸运”啦。

关于这几个方法,还有以下两点需要关注:

1.wait、notify、notifyAll这三个方法都是Object类中定义的,而Object类是所有类的父类,所以在java中的所有对象都会继承这三个方法。

2.这三个方法必须在同步块中被调用(之后会介绍同步块),如果在同步块之外调用这三个方法,java会抛出java.lang.IllegalMonitorStateException这个异常。

基于wait-notify机制的单生产者-单消费者模型

上面已经介绍了wait-notify机制用到的方法以及需要注意的点,实际上针对这个机制,有一个非常著名、非常经典的模型——生产者消费者模型。

什么是生产者-消费者模型呢?简单来说就是这么个场景:有两种线程分别是生产者线程和消费者线程,还有一个固定大小的资源队列。

生产者的任务是根据原料生产出产品,并将生产好的产品往队列里扔;消费者的任务呢就是从队列里面拿已经生产好的产品去进行包装。

我们可以看到在这个场景中,因为队列可容纳的资源是有限的,所以当队列满时,生产者就没办法继续往队列里放产品,此时生产者就需要等待消费者从队列里拿走产品后,才能继续往队列里放产品;

而消费者也是一样,当队列为空时,消费者就无法从队列里拿到产品,此时就需要等待生产者成功生产出产品并往队列里扔,才能继续从队列里拿产品。

这个场景是wait-notify机制最适合发挥作用的场景,下面是一个单生产者-单消费者的模拟代码:

  1 /**
  2  * 基于wait-notify机制的单生产者-消费者模型
  3  */
  4 public class ProducerAndConsumer {
  5
  6     public static void main(String[] args) {
  7         Resource resource = new Resource();
  8         //生产者线程
  9         ProducerThread p1 = new ProducerThread(resource);
 10         //消费者线程
 11         ConsumerThread c1 = new ConsumerThread(resource);
 12
 13         p1.start();
 14         c1.start();
 15
 16     }
 17 }
 18
 19
 20 /**
 21  * 公共资源类
 22  * @author
 23  *
 24  */
 25 class Resource{//重要
 26     //当前资源数量
 27     private int num = 0;
 28     //资源池中允许存放的资源数目
 29     private int size = 10;
 30
 31     /**
 32      * 从资源池中取走资源
 33      */
 34     public synchronized void remove(){
 35         if(num > 0){
 36             num--;
 37             System.out.println("消费者" + Thread.currentThread().getName() +
 38                     "消耗一件资源," + "当前线程池有" + num + "个");
 39             notifyAll();//通知生产者生产资源
 40         }else{
 41             try {
 42                 //如果没有资源,则消费者进入等待状态
 43                 wait();
 44                 System.out.println("消费者" + Thread.currentThread().getName() + "线程进入等待状态");
 45             } catch (InterruptedException e) {
 46                 e.printStackTrace();
 47             }
 48         }
 49     }
 50     /**
 51      * 向资源池中添加资源
 52      */
 53     public synchronized void add(){
 54         if(num < size){
 55             num++;
 56             System.out.println("生产者" + Thread.currentThread().getName() + "生产一件资源,当前资源池有"
 57                     + num + "个");
 58             //通知等待的消费者
 59             notifyAll();
 60         }else{
 61             //如果当前资源池中有10件资源
 62             try{
 63                 wait();//生产者进入等待状态,并释放锁
 64                 System.out.println(Thread.currentThread().getName()+"线程进入等待");
 65             }catch(InterruptedException e){
 66                 e.printStackTrace();
 67             }
 68         }
 69     }
 70 }
 71
 72
 73 /**
 74  * 消费者线程
 75  */
 76 class ConsumerThread extends Thread{
 77     private Resource resource;
 78     public ConsumerThread(Resource resource){
 79         this.resource = resource;
 80     }
 81     @Override
 82     public void run() {
 83         while(true){
 84             try {
 85                 Thread.sleep(1000);
 86             } catch (InterruptedException e) {
 87                 e.printStackTrace();
 88             }
 89             resource.remove();
 90         }
 91     }
 92 }
 93
 94
 95 /**
 96  * 生产者线程
 97  */
 98 class ProducerThread extends Thread{
 99     private Resource resource;
100     public ProducerThread(Resource resource){
101         this.resource = resource;
102     }
103     @Override
104     public void run() {
105         //不断地生产资源
106         while(true){
107             try {
108                 Thread.sleep(1000);
109             } catch (InterruptedException e) {
110                 e.printStackTrace();
111             }
112             resource.add();
113         }
114     }
115
116 }

童鞋们可以运行代码试试,这里资源池最大允许放10个产品。

这里留一个问题给大家思考,如果我这里的add和remove方法不加synchronized修饰,就会抛出java.lang.IllegalMonitorStateException异常,那么是什么原因导致java必须要这么做呢?我会在介绍synchronized关键字的时候公布答案。

好了,wait-notify机制到这里就介绍完毕,希望大家能够理解。下篇文章会为大家讲解一下volatile这个关键字的用法。

原文地址:https://www.cnblogs.com/smartchen/p/9280877.html

时间: 2024-10-08 03:14:51

线程间通信与协作方式之——wait-notify机制的相关文章

【线程间通信:等待唤醒机制】

在线程安全解决之后,还是一样存在着如下的问题: A:如果消费者先抢到CPU的执行权,就会去消费数据,但是现在的数据是默认值,没有意义,应该等着数据有意义,再消费. B:如果生产者先抢到CPU的执行权,就会去生产数据,但是呢,它生产完数据后,还继续拥有执行权,它又继续产生数据.这是有问题的,它应该等着消费者把数据消费掉,然后再生产. 正常的思路: A:生产者:先看是否有数据,有就等待,没有就生产,生产完后通知消费者来消费数据. B:消费者:先是是否有数据,有就消费,没有就等待,通知生产者生产数据.

【转】VC 线程间通信的三种方式

原文网址:http://my.oschina.net/laopiao/blog/94728 1.使用全局变量(窗体不适用)      实现线程间通信的方法有很多,常用的主要是通过全局变量.自定义消息和事件对象等来实现的.其中又以对全局变量的使用最为简洁.该方法将全局变量作为线程监视的对象,并通过在主线程对此变量值的改变而实现对子线程的控制.      由于这里的全局变量需要在使用它的线程之外对其值进行改变,这就需要通过volatile关键字对此变量进行说明.使用全局变量进行线程通信的方法非常简单

进程间通信和线程间通信的几种方式

进程间通信和线程间通信的几种方式 进程和线程的区别 概念 对于进程来说,子进程是父进程的复制品,从父进程那里获得父进程的数据空间,堆和栈的复制品. 而线程,相对于进程而言,是一个更加接近于执行体的概念,可以和同进程的其他线程之间直接共享数据,而且拥有自己的栈空间,拥有独立序列. 共同点 它们都能提高程序的并发度,提高程序运行效率和响应时间.线程和进程在使用上各有优缺点. 线程执行开销比较小,但不利于资源的管理和保护,而进程相反.同时,线程适合在SMP机器上运行,而进程可以跨机器迁移. 不同点 多

线程间通信的三种方式(NSThread,GCD,NSOperation)

一.NSThread线程间通信 #import "ViewController.h" @interface ViewController ()<UIScrollViewDelegate> @property (strong, nonatomic) IBOutlet UIScrollView *scrollView; @property (weak, nonatomic)  UIImageView *imageView; @end @implementation ViewCo

linux线程间通信之条件变量和互斥量

一.条件变量定义 有的时候仅仅依靠锁住共享资源来使用它是不够的.有时候共享资源只有某些状态的时候才能够使用.比方说,某个线程如果要从堆栈中读取数据,那么如果栈中没有数据就必须等待数据被压栈.这种情况下的同步使用互斥锁是不够的.另一种同步的方式--条件变量,就可以使用在这种情况下.条件变量(Condition Variable)是线程间的一种同步机制,提供给两个线程协同完成任务的一种方法,使用条件变量可以以原子方式阻塞线程,直到某个特定条件为真为止.条件变量的测试一般是用互斥量来保护的,用来确保每

NSThread&amp;线程间通信

创建和启动线程 一个NSThread对象就代表一条线程 创建.启动线程 NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; [thread start]; // 线程一启动,就会在线程thread中执行self的run方法 主线程相关用法 + (NSThread *)mainThread; // 获得主线程 - (BOOL)isMainThread; // 是否

java多线程系列5-死锁与线程间通信

这篇文章介绍java死锁机制和线程间通信 死锁 死锁:两个或两个以上的线程在争夺资源的过程中,发生的一种相互等待的现象. 同步代码块的嵌套案例 public class MyLock { // 创建两把锁对象 public static final Object objA = new Object(); public static final Object objB = new Object(); } public class DieLock extends Thread { private b

【黑马】程序员————多线程(二)单例设计模式、线程间通信,JDK1.5互斥锁

------Java培训.Android培训.iOS培训..Net培训.期待与您交流!----- 一.单例设计模式 单例设计模式的意义: A.保证类在内存中只有一个对象,不提供外部访问方式,构造函数用private修饰. B.提供公共方法(static修饰,类的静态方法),获取类的实例.单例设计模式分为饿汉和懒汉两种模式. 饿汉式&懒汉式 class Test33 { public static void main(String[] args) { Fanjianan.getInstance()

线程间通信与同步

线程间通信的两个基本问题是互斥和同步. 同步:一个线程的执行依赖于另一个线程的消息. 互斥:对共享资源的排他性,一个线程必须等待别的线程释放公共资源之后才能继续执行. 同步机制(Win32中):事件,信号量,互斥量,临界区 各种同步方式: #全局变量 win32多线程通信的最方式,但用全局变量同步会有两个弊端,应该避免 >主线程没有进入休眠状态,依然会消耗CPU资源 >如果主线程优先级比ThreadFunc高,则全局变量无法在ThreadFunc中被改变,这样线程无法得到通知 #事件 由于ev