同步阻塞队列使用以及原理

概要

阻塞队列(BlockingQueue)是由 java.util.concurrent 提供的用于解决并发生产者-消费者最常用的类,特点是在任意时刻只有一个线程可以执行存/取操作.
它额外支持在检索元素时等待队列变为非空,并在存储元素时等待队列中的空间变为可用的操作。

BlockingQueue方法有四种形式,它们有不同的方法来处理不能立即满足的操作,但可能在将来的某个时候得到满足:一种方法抛出异常,第二种方法返回一个特殊值(null或false,取决于操作),第三个无限期地阻塞当前线程,直到操作可以成功,而第四个阻塞只在给定的最大时间限制后放弃。这些方法总结如下:
在这里插入图片描述

队列类型

  • 无限队列 (unbounded queue ) - 可以无限增长(受内存大小限制)。
  • 有限队列( bounded queue)- 定义了最大容量

队列数据接口

  • 队列一般由链表或者数组来实现
  • 一般而言具备先FIFO特性

常见的4中阻塞度列

  • ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。

  • LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。

  • PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。

  • DelayQueue:一个使用优先级队列实现的无界阻塞队列。

ArrayBlockingQueue

一个由数组结构组成的有界阻塞队列,它遵循FIFO(first-in-first-out)原则,最先进入队列的成员为队列的头部,最晚进入队列的元素为尾部,每次执行插入操作是都把元素插入到队列的尾部,队列的检索操作是从队列的头部开始的.
队列的容量一旦创建就不能修改了,当向满容量的队列执行put操作的时候会导致线程阻塞,当向一个空队列执行take操作的时候也会导致线程阻塞.

成员变量

//下面为主要的成员变量

    final Object[] items; //队列容器

    int count;//队列中元素的个数
    
	 /** 独占锁,保证队列的存取操作始终只能由一个线程执行
	   *默认为非公平锁,可以在队列创建的时候指定公平/非公平锁
	   *改锁是以AQS中CLH队列为基础的,获取到锁的线程可以继续执行,获取不到锁的线程会被阻塞并保存到CLH队列中,知道锁被释放后被唤醒,继续去争抢锁
	   */
    final ReentrantLock lock;

    /** Condition for waiting takes
        Condition 为一个条件队列其子类为AbstractQueuedSynchronizer 中的内部类ConditionObject
     	ConditionObject 是一个由AbstractQueuedSynchronizer中内部类Node组成的单项链表-条件队列,
     	当阻塞队列empty的时候,执行take操作的线程(消费者)会执行notEmpty.await()阻塞并存入到notEmpty条件队
     	列中,等待执行put操作的线程(生产者)执行notEmpty.signal(); 发送信号,收到信号的消费者会从
     	notEmpty条件队列移入CLH队列中去等待独占锁释放后争抢锁
     */
    private final Condition notEmpty;

    /** Condition for waiting puts
      *当阻塞队列full(count == items.length) 的时候生产者会执行 notFull.await() 线程阻塞并存入到
      notFull条件队列中,等待消费者执行notFull.signal()发送型号,接受到信号的生产者会从notFull条件队列
      中移入到CLH队列中等待独占锁释放后争抢锁.
     */
    private final Condition notFull;

构造方法

   public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
            //初始化队列容量,一旦创建不能修改
        this.items = new Object[capacity];
        //初始化独占锁,默认为公平锁 也可以通过fair指定为非公平
        lock = new ReentrantLock(fair);
        //初始化 notEmpty 条件队列
        notEmpty = lock.newCondition();
        //初始化 notFull 条件队列
        notFull =  lock.newCondition();
    }

put(E e)操作

//插入到队列尾部
    public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();//可中断锁,当线程中断后抛异常
        try {
        // 当队列满的时候线程会被阻塞并存入到notFull条件队列中等待消费者发信号被唤醒
            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();//给消费者发信号
    }

take()操作

	//按照FIFO的原则 向外拿元素
    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
        //当队列为空的时候消费者线程阻塞并存入到notEmpty条件队列中,等待notFull发送信号
            while (count == 0)
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }
	//出队 并发送信号给生产者线程,唤醒生产者向阻塞队列执行put操作
    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--;
        if (itrs != null)
            itrs.elementDequeued();
        notFull.signal(); //发送信号
        return x;
    }

原理图示

在这里插入图片描述
由于图片大小问题,可能导致显示不清楚,大家可以加我v索要原理图示
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java阻塞队列是一种线程安全的数据结构,它提供了同步的功能,用于在多线程环境中安全地进行数据交换和通信。其实现原理主要涉及以下几个方面。 首先,阻塞队列的实现会使用锁机制确保线程安全。Java中可以使用ReentrantLock或synchronized关键字来实现锁,在对队列进行操作时会对其进行加锁,保证同一时刻只有一个线程能够访问队列。 其次,阻塞队列内部会使用条件变量或信号量来实现线程间的协调与通信。当队列为空时,消费者线程需要等待直到队列有数据可取;当队列已满时,生产者线程需要等待直到队列有空位置可放入新数据。通过条件变量或信号量的等待和唤醒机制,实现了线程间的同步和互斥。 此外,阻塞队列通常还会使用一个循环数组来存储数据。循环数组在插入和删除元素时能够高效地利用已分配的内存空间,避免了频繁的扩容和内存拷贝。同时,循环数组的读写指针可以通过取模运算得到,实现环形循环。 最后,阻塞队列还会根据不同的需求提供不同的阻塞操作。例如,用于插入元素的put()方法在队列已满时会阻塞直到有空位置可用,用于获取元素的take()方法在队列为空时会阻塞直到有数据可取。这些阻塞操作的实现依赖于同步和协调机制,保证了线程安全和数据一致性。 总之,Java阻塞队列通过使用锁、条件变量或信号量、循环数组等机制,实现了线程安全和线程间的同步与通信。它是多线程编程中常用的工具,能够有效地管理数据的生产和消费,提高多线程程序的可靠性和性能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值