Java同步互斥访问二(AQS框架)--> BlockingQueue

1、BlockingQueue简介

1.1、什么是BlockingQueue

BlockingQueue,也叫阻塞队列,它是线程通信的一个工具,在任意时刻,不管并发有多高,在单JVM上,同一时间永远只有一个线程能够对队列进行入队或者出队操作,类似于生产者、消费者模式。

1.2、BlockingQueue的api

添加元素

方法说明
add()如果插入成功则返回 true,否则抛出 IllegalStateException 异常
put()将指定的元素插入队列,如果队列满了,那么会阻塞直到有空间插入
offer()如果插入成功则返回 true,否则返回 false
offer(E e, long timeout, TimeUnit unit)尝试将元素插入队列,如果队列已满,那么会阻塞直到有空间插入,但是会有等待超时时间

检索元素

方法说明
take()获取队列的头部元素并将其删除,如果队列为空,则阻塞并等待元素变为可用
poll(long timeout, TimeUnit unit)检索并删除队列的头部,如有必要,等待指定的等待时间以使元素可用,如果超时,则返回 null

2、源码(ArrayBlockingQueue)

2.1、构造方法

public ArrayBlockingQueue(int capacity) {
    this(capacity, false);
}

// 点击上面代码的this即可进入到下面的代码
public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
        throw new IllegalArgumentException();
    this.items = new Object[capacity];
    // 创建一把锁,这个锁后续在put元素等时候用
    lock = new ReentrantLock(fair);
    // 条件对象,在take方法时使用
    notEmpty = lock.newCondition();
    // 条件对象,在put方法时使用
    notFull =  lock.newCondition();
}

2.2、put方法

先加锁,加上锁后,如果阻塞队列有空间,则继续生产,如果没有空间,则将生产者放到条件等待队列进行阻塞,直到有空间了之后便可以唤醒继续放入元素了,放完元素之后就得把锁释放掉了。
先放一张图

在这里插入图片描述

public void put(E e) throws InterruptedException {
    checkNotNull(e);
    // 定义一个锁
    final ReentrantLock lock = this.lock;
    // 去加锁
    lock.lockInterruptibly();
    try {
    	// 这里判断队列是否被放满
        while (count == items.length)
        	// 队列被放满了,进入条件等待队列阻塞,走2.2.1
	            notFull.await();
        // 队列没有被放满,直接入队,并释放一个signal信号,走2.2.3
        enqueue(e);
    } finally {
    	// 入队完成,释放锁资源,使得消费者可以获取到
        lock.unlock();
    }
}

2.2.1、notFull.await()

await方法会组成一个条件等待队列,将生产者的放到队列中,逐个释放锁的资源

public final void await() throws InterruptedException {
	// 判断当前线程是否被中断,如果是,直接抛出异常
    if (Thread.interrupted())
        throw new InterruptedException();
    // 组建一个条件等待队列,目的是将生产者放进来,因为现在已经满了,不能再生产了,走2.2.2
    Node node = addConditionWaiter();
    // 这里去释放锁,因为马上要进行阻塞,先释放掉锁,使得消费者可以去访问资源
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    // 下面这段代码的大概逻辑就是:
    // 将生产者全部放到条件等待队列中并park住,然后唤醒同步队列的第一个节点(也就是消费者)
    // 让它去消费,然后消费了之后会发一个signal信号给生产者,让他重新获取锁去生产数据
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    // 同步队列尝试获取锁,也就是消费者去消费相应的资源
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

2.2.2、addConditionWaiter()

这个方法是组成条件队列的方法:

private Node addConditionWaiter() {
    Node t = lastWaiter;
    // 这里就是去判断是否有无效的节点,如果有,就把它清除掉
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    // 将当前节点包装成一个条件节点
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    // 将头 firstWaiter 指向头节点,将 lastWaiter 指向尾节点
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

2.2.3、enqueue(E x)

入队,入队成功之后,给生产者发送一个signal信号。

private void enqueue(E x) {
    final Object[] items = this.items;
    items[putIndex] = x;
    if (++putIndex == items.length)
        putIndex = 0;
    count++;
    // 去发信号,并作一系列操作
    notEmpty.signal();
}

2.3、take()

取走 BlockingQueue 里排在首位的对象,若 BlockingQueue 为空,阻断进入等待状态直到 Blocking 有新的对象被加入为止。
先放一张图

在这里插入图片描述

public E take() throws InterruptedException {
	// 定义一个锁
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lockInterruptibly();
    try {
    	// 判断队列是否为空
        while (count == 0)
        	// 如果队列为空,走2.2.1
            notEmpty.await();
        // 出队,在出队的时候会向生产者发送一个signal信号
        // 如果生产者在条件等待队列中,则把它挪到同步队列中,继续生产资源,走2.3.3
        return dequeue();
    } finally {
    	// 释放锁
        lock.unlock();
    }
}

2.2.1、notEmpty.await()

await方法会组成一个条件等待队列,将消费者的放到队列中,逐个释放锁的资源,其实和2.1.1的await方法基本一致,只不过这次的条件从生产者变成了消费者。

public final void await() throws InterruptedException {
	// 判断当前线程是否被中断,如果是,直接抛出异常
    if (Thread.interrupted())
        throw new InterruptedException();
    // 组建一个条件等待队列,目的是将消费者放进来,因为队列已经为空了,没有资源可以消费了,走2.3.2
    Node node = addConditionWaiter();
    // 这里去释放锁,因为马上要进行阻塞,先释放掉锁,使得生产者可以获取锁并生产资源
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    // 下面这段代码的大概逻辑就是:
    // 将消费者全部放到条件等待队列中并park住,然后唤醒同步队列的第一个节点(也就是生产者)
    // 让它去生产,然后生产了之后会发一个signal信号给消费者,让他重新获取锁去消费数据
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    // 同步队列尝试获取锁,也就是生产者去生产相应的资源
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

2.3.2、addConditionWaiter()

这个方法是组成条件队列的方法:

private Node addConditionWaiter() {
    Node t = lastWaiter;
    // 这里就是去判断是否有无效的节点,如果有,就把它清除掉
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    // 将当前节点包装成一个条件节点
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    // 将头 firstWaiter 指向头节点,将 lastWaiter 指向尾节点
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

2.3.3、dequeue()

如果生产者在条件等待队列中,则把它挪到同步队列中,继续生产资源
出队,在出队的时候会向生产者发送一个signal信号

private E dequeue() {
    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();
    // 出队,在出队的时候会向生产者发送一个signal信号
    notFull.signal();
    return x;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值