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具有更高的吞吐,但是实际上还是看使用,这是不可预测的。

相关推荐
EPSDA24 分钟前
Java的IO流(二)
java·开发语言
zzlyyds1 小时前
SpringBoot---------Actuator监控
java·spring boot·spring·actuator
vitobo1 小时前
Java的JDBC编程
java·开发语言
呆萌小新@渊洁1 小时前
后端接收数组,集合类数据
android·java·开发语言
A_cot2 小时前
深入了解 Maven 和 Redis
java·redis·maven
liuyang-neu2 小时前
力扣中等 33.搜索旋转排序数组
java·数据结构·算法·leetcode
爱吃烤鸡翅的酸菜鱼2 小时前
java(3)数组的定义与使用
java·开发语言·idea·intellij idea
ganjiee00073 小时前
力扣(leetcode)每日一题 2414 最长的字母序连续子字符串的长度
java·算法·leetcode
kingbal3 小时前
SpringBoot:解析excel
java·开发语言·excel
远望樱花兔3 小时前
【d44】【Java】【力扣】160.相交链表
java·开发语言·leetcode