BlockingQueue的双锁源码解析

1.构造方法

public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node<E>(null);
    }

其中capacity是队列的长度,构造方法很简单,初始化node,并设置队列的最大容量capacity。
2.核心属性

/** The capacity bound, or Integer.MAX_VALUE if none */
//队列长度,不指定默认是Integer.MAX_VALUE
private final int capacity;
/** Current number of elements */
//当前队列的元素个数,用AtomicInteger 保证同步
private final AtomicInteger count = new AtomicInteger();
/** Lock held by take, poll, etc */
//take锁,类型为ReentrantLock
private final ReentrantLock takeLock = new ReentrantLock();
/** Wait queue for waiting takes */
//take的条件
private final Condition notEmpty = takeLock.newCondition();
/** Lock held by put, offer, etc */
//put锁,类型为ReentrantLock
private final ReentrantLock putLock = new ReentrantLock();
/** Wait queue for waiting puts */
//put的条件
private final Condition notFull = putLock.newCondition();

3.入队put

public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        int c = -1;
        Node<E> node = new Node<E>(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();
    }

第一步判断入队元素的合法性,第二步新建一个元素Node,然后请求锁并拿到当前队列的元素总数count,这些都比较容易理解,我们重点关注下try里的逻辑

1.先判断count等于队列最大长度capacity,此时用notFull阻塞等待,为什么这里不用if判断而是while呢?因为当阻塞被唤醒后,if会直接执行enqueue(node);操作,而在执行增加操作前可能又被其它线程拿到锁添加满了,所以必须再次判断才可以保证正确性。
2.完成入队后,判断c + 1 < capacity,然后随机唤醒一个notFull,这里为什么是唤醒一个消费者线程而不是唤醒全部呢?原因是有可能在队列满的时候假如共有5个生产线程,那么5个都会阻塞,这时消费者同时消费了多个元素,但是可能只发出了1个唤醒生产者的信号,这时候醒着的put线程就会通过这种方式来唤醒其它的4个put线程,以弥补take线程的信号不足。相比于signalAll()唤醒所有生产者,这种解决方案使得同一时间最多只有一个生产者在清醒的竞争锁,性能提升非常明显。
这里入队逻辑基本完成,出队逻辑是和入队对应的。

4.特殊情况
我们知道LinkedBlockingQueue是通过两把锁一把是put锁,一把是take锁,但是这样有一个特殊情况当队列长度为1时,到底入队和出队之间会存在锁竞争吗?
我们来看它是怎么做的

//初始化
public LinkedBlockingQueue(int capacity) {
    if (capacity <= 0) throw new IllegalArgumentException();
    this.capacity = capacity;
    last = head = new Node<E>(null);
}
//入队操作
private void enqueue(Node<E> node) {
    // assert putLock.isHeldByCurrentThread();
    // assert last.next == null;
    last = last.next = node;
}
//出队操作
private E dequeue() {
    // assert takeLock.isHeldByCurrentThread();
    // assert head.item == null;
    Node<E> h = head;
    Node<E> first = h.next;
    h.next = h; // help GC
    head = first;
    E x = first.item;
    first.item = null;
    return x;
}

1.初始化时,定义了一个dummy节点,这个和lock、countdownlatch实现一样,都有一个哨兵节点,head和tail都指向这个哨兵。
2.在队尾入队时,tail节点后移,并指向这个第一个入队的元素,此时head还是指向dummy。
3.出队时,创建一个Node h指向head也就是dummy,然后first指向head的next节点,然后把first的值赋值x,消除first,返回x。

总的来说就是互换head和head.next的值,最终把x返回.

感兴趣可以加Java架构师群获取Java工程化、高性能及分布式、高性能、深入浅出。高架构。性能调优、Spring,MyBatis,Netty源码分析和大数据等多个知识点高级进阶干货的直播免费学习权限 都是大牛带飞 让你少走很多的弯路的 群..号是:855801563 对了 小白勿进 最好是有开发经验

注:加群要求

1、具有工作经验的,面对目前流行的技术不知从何下手,需要突破技术瓶颈的可以加。

2、在公司待久了,过得很安逸,但跳槽时面试碰壁。需要在短时间内进修、跳槽拿高薪的可以加。

3、如果没有工作经验,但基础非常扎实,对java工作机制,常用设计思想,常用java开发框架掌握熟练的,可以加。

4、觉得自己很牛B,一般需求都能搞定。但是所学的知识点没有系统化,很难在技术领域继续突破的可以加。

5.阿里Java高级大牛直播讲解知识点,分享知识,多年工作经验的梳理和总结,带着大家全面、科学地建立自己的技术体系和技术认知!

转载于:https://blog.51cto.com/13981400/2324037

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值