JAVA并发(5)-并发队列LinkedBlockingQueue的分析

LinkedBlockingQueue的构造器

public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);
}

public LinkedBlockingQueue(int capacity) {
    if (capacity <= 0) throw new IllegalArgumentException();
    this.capacity = capacity;
    // head与last指向哨兵节点
    last = head = new Node<E>(null);
}

public LinkedBlockingQueue(Collection<? extends E> c)
...

1.2 保证线程安全
LinkedBlockingQueue的底层使用ReetrantLock保证线程安全,其实就是一个"消费-生产"模型,通过本文我们还可以学到ReetrantLock的实际使用场景。

/** Lock held by take, poll, etc */
private final ReentrantLock takeLock = new ReentrantLock();

/** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition();

/** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock();

/** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition();

/**
 * Signals a waiting take. Called only from put/offer (which do not
 * otherwise ordinarily lock takeLock.)
 */
private void signalNotEmpty() {
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
}

/**
 * Signals a waiting put. Called only from take/poll.
 */
private void signalNotFull() {
    final ReentrantLock putLock = this.putLock;
    putLock.lock();
    try {
        notFull.signal();
    } finally {
        putLock.unlock();
    }
}
  1. 源码分析
    在讲LinkedBlockingQueue前,我们先看看需要它实现的接口BlockingQueue

2.1 BlockingQueue
实现了BlockingQueue的类,必须额外支持在查找元素时,等待队列直到非空为止的操作;在储存元素的时候,要等待队列的空间可用为止。
它的方法有四种形式,处理不能立即满足但是未来可能满足的操作的方式各有不同。

直接抛出异常
返回一个特殊值(null 或者 false)
一直等待,直到操作成功
超时设定,超过时间就放弃
Summary of BlockingQueue methods

我们知道了不同方法,不能立即满足的不同的处理方式,这样我们下面就更好理解LinkedBlockingQueue的源码了。

下面我们从

offer(e)
offer(e, time, unit)
put(e)
poll()
去分析一下LinkedBlockingQueue
2.2 offer(e)
添加成功就返回true; 插入值为null,报错;或队列已满,直接返回false,不会等待队列空闲

public boolean offer(E e) {
if (e == null) throw new NullPointerException();
final AtomicInteger count = this.count;
// 队列的容量满了,就直接返回了
if (count.get() == capacity)
return false;
int c = -1;
Node node = new Node(e);
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
if (count.get() < capacity) {
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
}
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
return c >= 0;
}
代码较简单,就不细讲了。

2.3 offer(e, time, unit)
若队列已满,还没超过设定的时间,就等待,等待时,会对中断作出反应;若超过了设定的时间,操作就跟offer(E e)一样了

public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {

    if (e == null) throw new NullPointerException();
    long nanos = unit.toNanos(timeout);
    int c = -1;
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();
    try {
        while (count.get() == capacity) {
            // 队列已满,超过了特定的时间才会返回false
            if (nanos <= 0)
                return false;
            nanos = notFull.awaitNanos(nanos);
        }
        enqueue(new Node<E>(e));
        c = count.getAndIncrement();
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
    return true;
}

2.4 put(e)
一直等待直到成功或者被中断

public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
// Note: convention in all put/take/etc is to preset local var
// holding count negative to indicate failure unless set.
int c = -1;
Node node = new Node(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
// 防止虚假唤醒
while (count.get() == capacity) {
notFull.await();
}
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
我没有想到,上面发生虚假唤醒的场景(如果知道的同学,请告诉我一下,谢谢了)。

it is recommended that applications programmers always assume that they can occur and so always wait in a loop. --Condition

反正使用Condition在循环里等待就对了

2.5 poll()
队列为空时,直接返回null,不会await;非阻塞方法

public E poll() {
    final AtomicInteger count = this.count;
    if (count.get() == 0)
        return null;
    E x = null;
    int c = -1;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        if (count.get() > 0) {
            x = dequeue();
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        }
    } finally {
        takeLock.unlock();
    }
    if (c == capacity)
        signalNotFull();
    return x;
}

其余的方法其实都是类似的,直接看上面BlockingQueue的四种方式。

最后再讲一个方法remove(Object o), 将会提及一个知识点。

2.6 remove
删除o, 若成功就返回true,反之.

public boolean remove(Object o) {
if (o == null) return false;
fullyLock();
try {
// 算法题,删除某一个链表的结点,可以看一下源码,记录两个结点,一个在前,一个在后。
for (Node trail = head, p = trail.next;
p != null;
trail = p, p = p.next) {
if (o.equals(p.item)) {
unlink(p, trail);
return true;
}
}
return false;
} finally {
fullyUnlock();
}
}
上面的代码是简单的对链表的操作,我们主要是看fullyLock() 与 fullyUnlock()的代码

/**
 * Locks to prevent both puts and takes.
 */
void fullyLock() {
    putLock.lock();
    takeLock.lock();
}

/**
 * Unlocks to allow both puts and takes.
 */
void fullyUnlock() {
    takeLock.unlock();
    putLock.unlock();
}

我们都知道解锁顺序应该与获取锁顺序相反,那么是为什么啦

其实我并不觉得,上面的fullyUnlock解锁顺序与获取锁的顺序如果是相同的会出什么问题,也并不会出现死锁(如果释放锁与获取锁,中间还存在其他操作就另当别论了)。那它仅仅是为了代码的好看?

假如,我们有下面这段代码(解锁与获取锁中间有其他操作)

A.lock();
B.lock();
Foo();
A.unlock();
Bar();
B.unlock();
USB Microphone https://www.soft-voice.com/
Wooden Speakers https://www.zeshuiplatform.com/
亚马逊测评 www.yisuping.cn
深圳网站建设www.sz886.com

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值