简单理解阻塞队列(BlockingQueue)中的take/put方法以及Condition存在的作用

简单理解阻塞队列(BlockingQueue)中的take/put方法以及Condition存在的作用

  • Condition:可以理解成一把锁的一个钥匙,它既可以解锁(通知放行),又可以加锁(阻塞)

  • notFull:当队列元素满了时,阻塞生产,当队列元素存在元素但是没有满时,去通知消费

  • notEmpty:当队列中不存在元素时,阻塞消费,当队列元素存在元素时,去通知生产

while (count >= datas.length) {...}
while (count <= 0) {...}
两个while循环判断,而不用if,是因为线程不安全,
导致多线程场景下每个线程获取到的循环条件count的值存在差异,
导致代码执行异常,用while可以使当前线程重新刷新判断条件count的值。
  • 用处:
    ThreadPoolExecutor中使用到了阻塞队列,阻塞队列中又使用到了ReentrantLock,而ReentrantLock基于AQS。
package com.zhuyz.foundation.juc;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class MyArrayBlockingQueue<T> {

    // 数组元素
    private T[] datas;

    // 实际的元素个数(也有索引下标的作用)
    private volatile int count;

    private final ReentrantLock putLock = new ReentrantLock();

    private final ReentrantLock takeLock = new ReentrantLock();

    // 通知消费,暂停生产【当数组full时await(put时),当数组notFull时signal(take时)】
    private Condition notFull = putLock.newCondition();

    // 通知生产,暂停消费【当数组empty时await(take时),当数组notEmpty时signal(put时)】
    private Condition notEmpty = takeLock.newCondition();

    public MyArrayBlockingQueue(int cap) {
        this.datas = (T[]) new Object[cap];
    }

    private void put(T t) {
        final ReentrantLock putLock = this.putLock;
        putLock.lock();
        try {
            // 线程不安全,需要循环判断,插入值之前判断一下数组长度是否达到最大长度
            while (count >= datas.length) {
                System.out.println("datas is full, please waiting take");
                notFull.await();
            }
            datas[count++] = t;
            System.out.println("put: " + t);
        } catch (Exception e) {
            System.out.println("异常" + e);
        } finally {
            putLock.unlock();
        }
        // 通知获取元素的线程继续执行(take_thread)
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();
        try {
            notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
    }

    private T take() {
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();
        T t = null;
        try {
            // 线程不安全,需要循环判断,插入值之前判断一下数组中元素个数是否为0
            while (count <= 0) {
                System.out.println("datas is empty, please waiting put");
                notEmpty.await();
            }
            // 获取元素
            t = datas[--count];
            System.out.println("take: " + t);
        } catch (Exception e) {
            System.out.println("异常" + e);
        } finally {
            takeLock.unlock();
        }
        final ReentrantLock putLock = this.putLock;
        putLock.lock();
        try {
            // 通知插入元素的线程继续执行(put_thread)
            notFull.signal();
        } finally {
            putLock.unlock();
        }
        return t;
    }

    public static void main(String[] args) throws InterruptedException {
        MyArrayBlockingQueue<Integer> myArrayBlockingQueue = new MyArrayBlockingQueue<>(5);
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            new Thread(() -> myArrayBlockingQueue.put(finalI)).start();
        }
        TimeUnit.SECONDS.sleep(5L);
        for (int i = 0; i < 5; i++) {
            new Thread(() -> myArrayBlockingQueue.take()).start();
        }
    }
}

结果如下:
从结果中可以看出,先put了5个元素,然后另外五个元素被阻塞住了,没有进去
take消费5个之后,另外五个被阻塞的元素就被put进去了

put: 0
put: 1
put: 2
put: 3
put: 4
datas is full, please waiting take
datas is full, please waiting take
datas is full, please waiting take
datas is full, please waiting take
datas is full, please waiting take
take: 4
put: 5
take: 5
take: 3
put: 6
put: 7
take: 7
put: 8
take: 8
put: 9

  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
Java的Map是一种键值对的数据结构,它提供了快速的访问和查找功能。但是,Map并不是一个阻塞队列,它不能直接用来实现阻塞队列功能。但是,我们可以使用Map来实现一个阻塞队列,具体实现方法如下: 1. 创建一个Map对象,用来存储队列元素。 2. 创建一个互斥锁对象,用来保证队列操作的线程安全。 3. 创建两个条件变量,一个用来表示队列已满,另一个用来表示队列为空。 4. 实现队列的put方法,当队列已满时,阻塞当前线程,等待队列有空闲位置。 5. 实现队列的take方法,当队列为空时,阻塞当前线程,等待队列有元素可取。 6. 实现队列的size方法,返回队列元素的数量。 下面是一个使用Map实现阻塞队列的示例代码: ``` import java.util.Map; import java.util.HashMap; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class BlockingQueue<T> { private final Map<Integer, T> map; private final Lock lock; private final Condition notFull; private final Condition notEmpty; private int capacity; private int head; private int tail; public BlockingQueue(int capacity) { this.capacity = capacity; map = new HashMap<>(capacity); lock = new ReentrantLock(); notFull = lock.newCondition(); notEmpty = lock.newCondition(); head = 0; tail = 0; } public void put(T element) throws InterruptedException { lock.lock(); try { while (tail - head == capacity) { notFull.await(); } map.put(tail % capacity, element); tail++; notEmpty.signal(); } finally { lock.unlock(); } } public T take() throws InterruptedException { lock.lock(); try { while (tail == head) { notEmpty.await(); } T element = map.remove(head % capacity); head++; notFull.signal(); return element; } finally { lock.unlock(); } } public int size() { lock.lock(); try { return tail - head; } finally { lock.unlock(); } } } ``` 在这个示例代码,我们使用了一个Map来存储队列元素,并使用了一个互斥锁和两个条件变量来实现阻塞队列的功能。put方法和take方法分别实现了向队列添加元素和从队列取出元素的功能。size方法返回队列元素的数量。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值