生产者等待java_Java等待-通知机制与生产者消费者模型

一、什么是等待-通知机制

如果线程要求的条件不满足,则线程阻塞自己,进入等待状态;当线程要求的条件满足后,通知等待的线程重新执行。用简单的代码来理解:

synchronized(锁对象){

while (条件不满足){

wait();

}

//条件满足时

//do sthing

notify();

}

这可以看成是多线程编程中的一种范式,需要注意这里必须使用while,因为执行wait()方法进入阻塞后会停留在这里,当被其他线程唤醒后,需要再一次判断条件是否满足,因为这时有可能资源已经别的线程先抢占了。 还有一点需要注意wait()和notify()必须在同步代码块中使用(就是已经获得锁了的情况下)。这是因为线程状态转换是有规定的,比如线程只能从running状态进入waiting状态。如果不是这样的情况就会报 java.lang.IllegalMonitorStateException()错误。

二、生产者消费者模型

1.理解有缓冲区的生产者消费者模型。

8ba98bea481e

image.png

生产者消费者可以说是等待通知机制的典型应用了,直接看代码,使用wait(),notify()实现一个简单的生产者消费者模型

/**

* @author xiaobenneng@hotmail.com

* @date 2020/3/22

*/

public class ProducerComsumer {

LinkedList list = new LinkedList<>();//缓冲区

public static void main(String[] args) throws InterruptedException {

ProducerComsumer producerComsumer = new ProducerComsumer();

Thread thread1 = new Thread(()->{

while (true) {

try {

producerComsumer.producer();

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

}

}

});

Thread thread2 = new Thread(()->{

while (true) {

try {

producerComsumer.comsumer();

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

}

}

});

thread1.start();

thread2.start();

}

public void producer() throws InterruptedException {

Random random = new Random();

synchronized (this) {

//当容器满时就等待消费者消费数据,容器不满时就往里面放数据

while (list.size() == 5) {

wait();

}

int i = random.nextInt(10);

list.add(i);

System.out.printf("当前线程%s生产了数据%d \n", Thread.currentThread().getName(), i);

notifyAll();

}

}

public void comsumer() throws InterruptedException {

synchronized (this) {

//当容器为空时,就等待生产者往里面放数据。容器不为空时就消费数据

while (list.size() == 0) {

wait();

}

Integer remove = list.removeFirst();

System.out.printf("当前线程%s消费了%d \n", Thread.currentThread().getName(), remove);

notifyAll();

}

}

}

如果对锁的机制还不太熟悉的话,理解这个例子需要注意的是wait(),notify()只能唤醒同一个对象锁中的等待线程。在这里这个锁对象是this。

使用Lock ,Condition实现生产者消费者模型

先看一下java.util.concurrent包中的阻塞队列是如何实现阻塞的(仅讨论take()和put()方法,这两个方法在条件不满足时会阻塞一直等待)。

几个关键的参数:

/** 这个队列中都使用这把锁 */

private ReentrantLock lock;

/** 存放元素的数组 */

private Object[] items;

/** 锁对应的数组元素为空时的条件 */

private Condition notEmpty;

/** 锁对应的数组元素为满的条件 */

private Condition notFull;

/** 数组中元素的数量 */

int count;

这几个参数会在构造方法中初始化。

public ArrayBlockingQueue(int capacity, boolean fair) {

if (capacity <= 0)

throw new IllegalArgumentException();

this.items = new Object[capacity];

lock = new ReentrantLock(fair);

notEmpty = lock.newCondition();

notFull = lock.newCondition();

}

在生产者方面,调用存数据的方法,当数据满了时就调用notFull.await()(相当于Object.wait())进入等待,直到消费者消费了数据以后,通过调用notFull.signal()(相当于Object.notify())来唤醒生产者继续生产。下面抽取2个主要的方法:

public void put(E e) throws InterruptedException {

final ReentrantLock lock = this.lock;

lock.lockInterruptibly();

try {

while (count == items.length)

notFull.await();

enqueue(e);

} finally {

lock.unlock();

}

}

private void enqueue(E x) {

// assert lock.getHoldCount() == 1;

// assert items[putIndex] == null;

final Object[] items = this.items;

items[putIndex] = x;

if (++putIndex == items.length)

putIndex = 0;

count++;

notEmpty.signal();

}

在消费者方面,调用取数据的方法,当数据为空时就调用notEmpty.await()进入等待,直到生产者生产了数据以后,通过调用notEmpty.signal()来唤醒消费者者继续生产。下面看一下主要的两个方法:

public E take() throws InterruptedException {

final ReentrantLock lock = this.lock;

lock.lockInterruptibly();

try {

while (count == 0)

notEmpty.await();

return dequeue();

} finally {

lock.unlock();

}

}

private E dequeue() {

// assert lock.getHoldCount() == 1;

// assert items[takeIndex] != null;

final Object[] items = this.items;

@SuppressWarnings("unchecked")

E x = (E) items[takeIndex];

items[takeIndex] = null;

if (++takeIndex == items.length)

takeIndex = 0;

count--;

notFull.signal();

return x;

}

可以看到这是很明显的等待-通知的形式了。

接下来可以模仿ArrayBlockingQueue这种方式实现生产者消费者模型。

/**

* @author xiaobenneng@hotmail.com

* @date 2020/3/22

*/

public class ProducerComsumer {

public static void main(String[] args) {

ProducerComsumer producerComsumer = new ProducerComsumer(5);

new Thread(()->{

Random random = new Random();

while (true) {

int i = random.nextInt(10);

try {

producerComsumer.put(i);

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

}

}

}).start();

new Thread(()->{

while (true) {

try {

Integer take = producerComsumer.take();

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

}

}

}).start();

}

private ReentrantLock lock;

/** 存放元素的链表 */

private LinkedList items;

/** 锁对应的数组元素为空时的条件 */

private Condition notEmpty;

/** 锁对应的数组元素为满的条件 */

private Condition notFull;

/** 链表中最大元素的数量 */

int capacity;

public ProducerComsumer(int capacity) {

if (capacity <= 0)

throw new IllegalArgumentException();

this.items = new LinkedList();

this.capacity = capacity;

lock = new ReentrantLock();

notEmpty = lock.newCondition();

notFull = lock.newCondition();

}

public void put(E e) throws InterruptedException {

final ReentrantLock lock = this.lock;

lock.lockInterruptibly();

try {

while (capacity == items.size()) {

notFull.await();

}

items.addLast(e);

System.out.printf("当前线程%s生产数据%d,总数%d \n",Thread.currentThread().getName(), e,items.size());

notEmpty.signal();

} finally {

lock.unlock();

}

}

public E take() throws InterruptedException {

final ReentrantLock lock = this.lock;

lock.lockInterruptibly();

try {

while (items.size() == 0) {

notEmpty.await();

}

E e = items.removeFirst();

System.out.printf("当前线程%s消费数据%d,剩余总数%d \n",Thread.currentThread().getName(),e, items.size());

notFull.signal();

return e;

} finally {

lock.unlock();

}

}

}

测试结果

当前线程Thread-0生产数据5,总数1

当前线程Thread-0生产数据1,总数2

当前线程Thread-0生产数据0,总数3

当前线程Thread-0生产数据7,总数4

当前线程Thread-0生产数据8,总数5

当前线程Thread-1消费数据5,剩余总数4

当前线程Thread-1消费数据1,剩余总数3

当前线程Thread-1消费数据0,剩余总数2

当前线程Thread-1消费数据7,剩余总数1

当前线程Thread-1消费数据8,剩余总数0

模拟一种场景,当生产力大于消费力时,通过在消费者端sleep一段时间模拟这个场景

public static void main(String[] args) {

ProducerComsumer producerComsumer = new ProducerComsumer(5);

Random random = new Random();

new Thread(()->{

while (true) {

int i = random.nextInt(10);

try {

producerComsumer.put(i);

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

}

}

}).start();

new Thread(()->{

while (true) {

try {

Integer take = producerComsumer.take();

//处理数据花费时间

Thread.sleep(0,random.nextInt(500));

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

}

}

}).start();

new Thread(()->{

while (true) {

try {

Integer take = producerComsumer.take();

//处理数据花费时间

Thread.sleep(0,random.nextInt(400));

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

}

}

}).start();

}

运行结果

当前线程Thread-0生产数据6,总数4

当前线程Thread-0生产数据7,总数5

当前线程Thread-1消费数据0,剩余总数4

当前线程Thread-2消费数据6,剩余总数3

当前线程Thread-0生产数据5,总数4

当前线程Thread-0生产数据9,总数5

当前线程Thread-1消费数据5,剩余总数4

当前线程Thread-2消费数据6,剩余总数3

当前线程Thread-0生产数据7,总数4

当前线程Thread-0生产数据4,总数5

生产者生产数据的速度远远大过消费者消费数据速度,这体现了这种模式的优势,生产者不依赖消费者,生产的数据先放入缓存区,消费者再慢慢消费,处理结果使用异步返回。对于消费者来讲,因为消费者处理任务的能力是有限的,这时候也对服务器资源起到了保护的作用,比如可以避免过多的请求发送到mysql。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值