java并发编程 LinkedBlockingQueue详解

文章目录

  • 前言
  • [1 LinkedBlockingQueue是什么](#1 LinkedBlockingQueue是什么)
  • [2 核心属性详解](#2 核心属性详解)
  • [3 核心方法详解](#3 核心方法详解)
    • [3.1 offer(E e)](#3.1 offer(E e))
    • [3.2 put(E e)](#3.2 put(E e))
    • [3.3 take()](#3.3 take())
    • [3.4 poll()](#3.4 poll())
    • [3.5 peek()](#3.5 peek())
    • [3.6 remove(Object o)](#3.6 remove(Object o))
    • [3.7 drainTo(Collection<? super E> c, int maxElements)](#3.7 drainTo(Collection<? super E> c, int maxElements))
  • 总结

前言

学习LinkedBlockingQueue需要掌握ReentrantLock 原理,或者了解其使用也行。
java 并发编程系列文章目录

1 LinkedBlockingQueue是什么

在LinkedBlockingQueue注释上已经写明:

基于链表节点的可选有界阻塞队列。该队列对元素FIFO(先进先出)进行排序。队列的头是在队列中停留时间最长的元素。队列的尾部是在队列中停留时间最短的元素。新元素被插入到队列的尾部,队列检索操作获得队列头部的元素

2 核心属性详解

使用两把锁那保证放入和获取时操作的线程安全性

java 复制代码
//指定的容量 默认int最大值
private final int capacity;

//当前集合中的元素数量,AtomicInteger 保证原子性,不使用基本类型是因为这里有两把锁,不像ArrayBlockingQueue是一个ReentrantLock 
private final AtomicInteger count = new AtomicInteger();

//链表的头结点 和 尾结点
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();

3 核心方法详解

3.1 offer(E e)

java 复制代码
    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<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        //上putLock
        putLock.lock();
        try {
        	//再次判断数量
            if (count.get() < capacity) {
            	//放入链表中
                enqueue(node);
                c = count.getAndIncrement();
                if (c + 1 < capacity)
                	//为啥这地方要唤醒添加元素阻塞的线程呢,因为可能当时这个线程添加的时候已经满了,然后阻塞了,此时另外一个线程去除几个元素,还没唤醒线程,同时这边已经放入一个线程,此时还是可以添加元素的,所以这样会更快的让阻塞的线程被唤醒进行添加元素
                    notFull.signal();
            }
        } finally {
            putLock.unlock();
        }
        //这个C == 0的情况和notFull.signal()一样,具体原因就是takeLock和putLock是两把不同的锁,所以这属于优化
        if (c == 0)
            signalNotEmpty();
        return c >= 0;
    }

3.2 put(E e)

相对于offer方法 多了notFull.await(); 当元素满的时候会阻塞等待元素被取走,然后添加元素

java 复制代码
    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) {
            	//和offer区别在这
                notFull.await();
            }
            enqueue(node);
            c = count.getAndIncrement();
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
    }

3.3 take()

从链表中拿出一个元素

java 复制代码
    public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        //takeLock获取锁
        takeLock.lockInterruptibly();
        try {
            while (count.get() == 0) {
            	//当没有数据获取的时候会阻塞在这等待元素的放入
                notEmpty.await();
            }
            //从链表的头部获取一个元素,因为是尾部添加元素,头部获取元素
            x = dequeue();
            //c > 1的情况上面以描述,两把锁的原因
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        //同上
        if (c == capacity)
            signalNotFull();
        return x;
    }

3.4 poll()

相对于take方法,如果没有元素会返回null

java 复制代码
    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;
        //获取take锁
        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;
    }

3.5 peek()

从链表中获取个元素,但是没有从链表中删除

java 复制代码
    public E peek() {
        if (count.get() == 0)
            return null;
        final ReentrantLock takeLock = this.takeLock;
        //获取take锁
        takeLock.lock();
        try {
        	//查询到头部的元素返回
            Node<E> first = head.next;
            if (first == null)
                return null;
            else
                return first.item;
        } finally {
            takeLock.unlock();
        }
    }

3.6 remove(Object o)

java 复制代码
    public boolean remove(Object o) {
        if (o == null) return false;
        //remove一个元素,此时需要对take 和 put两把锁都需要获取到才行,因为take不能拿到被删除的
        //元素,put的时候如果删除的尾部节点元素呢,也会线程不安全
        fullyLock();
        try {
        	//遍历查找这个元素,找到就从链表中移除该元素
            for (Node<E> 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();
        }
    }

3.7 drainTo(Collection<? super E> c, int maxElements)

把所少个元素放入指定的集合中

java 复制代码
    public int drainTo(Collection<? super E> c, int maxElements) {
        if (c == null)
            throw new NullPointerException();
        if (c == this)
            throw new IllegalArgumentException();
        if (maxElements <= 0)
            return 0;
        boolean signalNotFull = false;
        final ReentrantLock takeLock = this.takeLock;
        //因为你要拿出一些元素放入到指定的集合中,所以是获取take锁
        takeLock.lock();
        try {
        	//取最小值,因为可能元素数量小于需要的元素数量
            int n = Math.min(maxElements, count.get());
            // count.get provides visibility to first n Nodes
            Node<E> h = head;
            int i = 0;
            try {
            	//获取元素进行添加,并从链表中移除
                while (i < n) {
                    Node<E> p = h.next;
                    c.add(p.item);
                    p.item = null;
                    h.next = h;
                    h = p;
                    ++i;
                }
                return n;
            } finally {
                ...
            }
        } finally {
            takeLock.unlock();
            if (signalNotFull)
                signalNotFull();
        }
    }

总结

LinkedBlockingQueue 是基于链表实现的阻塞队列。它相对于数组添加和删除的锁粒度变小,在增删方面线程同步阻塞的概率就会变小。同样也是用ReentrantLock来保证线程安全。置于和ArrayBlockingQueue谁的性能好,理论上来说LinkedBlockingQueue具有更高的吞吐,但是实际上还是看使用,这是不可预测的。

相关推荐
缺点内向3 小时前
Java:创建、读取或更新 Excel 文档
java·excel
带刺的坐椅3 小时前
Solon v3.4.7, v3.5.6, v3.6.1 发布(国产优秀应用开发框架)
java·spring·solon
四谎真好看5 小时前
Java 黑马程序员学习笔记(进阶篇18)
java·笔记·学习·学习笔记
桦说编程5 小时前
深入解析CompletableFuture源码实现(2)———双源输入
java·后端·源码
java_t_t5 小时前
ZIP工具类
java·zip
lang201509285 小时前
Spring Boot优雅关闭全解析
java·spring boot·后端
pengzhuofan6 小时前
第10章 Maven
java·maven
百锦再7 小时前
Vue Scoped样式混淆问题详解与解决方案
java·前端·javascript·数据库·vue.js·学习·.net
刘一说7 小时前
Spring Boot 启动慢?启动过程深度解析与优化策略
java·spring boot·后端
壹佰大多7 小时前
【spring如何扫描一个路径下被注解修饰的类】
java·后端·spring