首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

Java 容器源码分析之ArrayBlockingQueue和LinkedBlockingQueue(3)

Java 容器源码分析之ArrayBlockingQueue和LinkedBlockingQueue(3)

LinkedBlockingQueueLinkedBlockingQueue是一个使用链表完成队列操作的阻塞队列。链表是单向链表,而不是双向链表。
内部使用放锁和拿锁,这两个锁实现阻塞(“two lock queue” algorithm)。
它带有的属性如下:


// 容量大小private final int capacity;// 元素个数,因为有2个锁,存在竞态条件,使用AtomicIntegerprivate final AtomicInteger count = new AtomicInteger(0);// 头结点private transient Node<E> head;// 尾节点private transient Node<E> last;// 拿锁private final ReentrantLock takeLock = new ReentrantLock();// 拿锁的条件对象private final Condition notEmpty = takeLock.newCondition();// 放锁private final ReentrantLock putLock = new ReentrantLock();// 放锁的条件对象private final Condition notFull = putLock.newCondition();[url=][/url]

ArrayBlockingQueue只有1个锁,添加数据和删除数据的时候只能有1个被执行,不允许并行执行。
而LinkedBlockingQueue有2个锁,放锁和拿锁,添加数据和删除数据是可以并行进行的,当然添加数据和删除数据的时候只能有1个线程各自执行。
数据的添加LinkedBlockingQueue有不同的几个数据添加方法,add、offer、put方法。
add方法内部调用offer方法:
[url=][/url]
public boolean offer(E e) {    if (e == null) throw new NullPointerException(); // 不允许空元素    final AtomicInteger count = this.count;    if (count.get() == capacity) // 如果容量满了,返回false        return false;    int c = -1;    Node<E> node = new Node(e); // 容量没满,以新元素构造节点    final ReentrantLock putLock = this.putLock;    putLock.lock(); // 放锁加锁,保证调用offer方法的时候只有1个线程    try {        if (count.get() < capacity) { // 再次判断容量是否已满,因为可能拿锁在进行消费数据,没满的话继续执行            enqueue(node); // 节点添加到链表尾部            c = count.getAndIncrement(); // 元素个数+1            if (c + 1 < capacity) // 如果容量还没满                notFull.signal(); // 在放锁的条件对象notFull上唤醒正在等待的线程,表示可以再次往队列里面加数据了,队列还没满        }    } finally {        putLock.unlock(); // 释放放锁,让其他线程可以调用offer方法    }    if (c == 0) // 由于存在放锁和拿锁,这里可能拿锁一直在消费数据,count会变化。这里的if条件表示如果队列中还有1条数据        signalNotEmpty(); // 在拿锁的条件对象notEmpty上唤醒正在等待的1个线程,表示队列里还有1条数据,可以进行消费    return c >= 0; // 添加成功返回true,否则返回false}[url=][/url]

put方法:
[url=][/url]
public void put(E e) throws InterruptedException {    if (e == null) throw new NullPointerException(); // 不允许空元素    int c = -1;    Node<E> node = new Node(e); // 以新元素构造节点    final ReentrantLock putLock = this.putLock;    final AtomicInteger count = this.count;    putLock.lockInterruptibly(); // 放锁加锁,保证调用put方法的时候只有1个线程    try {        while (count.get() == capacity) { // 如果容量满了            notFull.await(); // 阻塞并挂起当前线程        }        enqueue(node); // 节点添加到链表尾部        c = count.getAndIncrement(); // 元素个数+1        if (c + 1 < capacity) // 如果容量还没满            notFull.signal(); // 在放锁的条件对象notFull上唤醒正在等待的线程,表示可以再次往队列里面加数据了,队列还没满    } finally {        putLock.unlock(); // 释放放锁,让其他线程可以调用put方法    }    if (c == 0) // 由于存在放锁和拿锁,这里可能拿锁一直在消费数据,count会变化。这里的if条件表示如果队列中还有1条数据        signalNotEmpty(); // 在拿锁的条件对象notEmpty上唤醒正在等待的1个线程,表示队列里还有1条数据,可以进行消费}


LinkedBlockingQueue的添加数据方法add,put,offer跟ArrayBlockingQueue一样,不同的是它们的底层实现不一样。ArrayBlockingQueue中放入数据阻塞的时候,需要消费数据才能唤醒。
而LinkedBlockingQueue中放入数据阻塞的时候,因为它内部有2个锁,可以并行执行放入数据和消费数据,不仅在消费数据的时候进行唤醒插入阻塞的线程,同时在插入的时候如果容量还没满,也会唤醒插入阻塞的线程。
返回列表