Source LinkedBlockingQueue

table of Contents

Introduction

data structure

List node

Attributes

Way to achieve

offer

poll

peek

put

take

enqueue,dequeue

signal

fullLock

note

reference


Introduction

java.util.concurrent.LinkedBlockingQueueIt is based on a one-way linked list of an arbitrary range ( in fact bounded ), the FIFO queue blocking . Access and removal operations are carried out in the first team, adding the operation was carried out in the tail, and are using different lock protection, only operation may involve multiple nodes simultaneously for only two locks for locking .

The simultaneous use of two locks, when the need to use two locks, lock release order and order is very important: You must be locked in a fixed order, then the order of the lock to release the lock reverse order. Lock uses ReentrantLock, Reference: ReentrantLock source .

Head node and tail node always points to the beginning of a node sentinel, it does not hold the actual data when there is data in the queue, the head remains pointing to the sentinel node, end node points to the last valid data knot point . The advantage of this is that, upon binding to a counter COUNT, of the team head, can be accessed independently at the tail, without the need to determine the relationship between the first node and the end node.

data structure

List node

    static class Node<E> {
        E item;

        /**
         * 后继指针。值为下列之一:
         * 实际的后继结点。
         * 自身,表示后继是 head.next (用于在遍历处理时判断)
         * null,表示没有后继(这是尾结点)
         */
        Node<E> next;

        Node(E x) { item = x; }
    }

Attributes

// 最大容量上限,默认是 Integer.MAX_VALUE
private final int capacity;

// 当前元素数量,这是个原子类。因为读写分别使用不同的锁,但都会访问这个属性,所以它需要是线程安全的。
private 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();

Way to achieve

offer

    
    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.lock();
        try {
            if (count.get() < capacity) {
                //节点入队列
                enqueue(node);
                c = count.getAndIncrement();
                //队列未满,则唤醒其他put线程
                if (c + 1 < capacity)
                    notFull.signal();
            }
        } finally {
            putLock.unlock();
        }

        /*当c=0时,即意味着之前的队列是空队列,出队列的线程都处于等待状态,
        现在新添加了一个新的元素,即队列不再为空,因此它会唤醒正在等待获取元素的线程。
        */
        if (c == 0)
            signalNotEmpty();
        return c >= 0;
    }
    
    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;
                //否则继续等待notFull
                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;
    }

poll


    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        E x = null;
        int c = -1;
        long nanos = unit.toNanos(timeout);
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        //调用可中断方法
        takeLock.lockInterruptibly();
        try {
            //队列空,则等待
            while (count.get() == 0) {
                if (nanos <= 0)
                    return null;
                nanos = notEmpty.awaitNanos(nanos);
            }
            //出队
            x = dequeue();
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }

    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;
    }

peek

    public E peek() {
        if (count.get() == 0)
            return null;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();
        try {
            //获取第一个元素
            Node<E> first = head.next;
            if (first == null)
                return null;
            else
                return first.item;
        } finally {
            takeLock.unlock();
        }
    }

put

public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();

    // 在所有的 put/take/etc 等操作中预设值本地变量 c 为负数表示失败。成功会设置为 >= 0 的值。
    int c = -1;
    Node<E> node = new Node(e);

    // 下面两行是访问优化
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;

    putLock.lockInterruptibly();
    try {
        /*
         * 注意,count用于等待监视,即使它没有用锁保护。这个可行是因为
         * count 只能在此刻(持有putLock)减小(其他put线程都被锁拒之门外),
         * 当count对capacity发生变化时,当前线程(或其他put等待线程)将被通知。
         * 在其他等待监视的使用中也类似。
         */
        while (count.get() == capacity) {
            notFull.await();
        }

        enqueue(node);
        c = count.getAndIncrement();

        // 还有可添加空间则唤醒put等待线程。
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        putLock.unlock();
    }

        /*当c=0时,即意味着之前的队列是空队列,出队列的线程都处于等待状态,
        现在新添加了一个新的元素,即队列不再为空,因此它会唤醒正在等待获取元素的线程。
        */
    if (c == 0)
        signalNotEmpty();
}

take

public E take() throws InterruptedException {
    E x;
    int c = -1;
    // 下面两行是访问优化
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;

    takeLock.lockInterruptibly();
    try {
      // 循环里等待直到有数据可获取
        while (count.get() == 0) {
            notEmpty.await();
        }

        // 获取第一个有效元素
        x = dequeue();

        // 如果还有可获取元素,唤醒等待获取的线程。
        c = count.getAndDecrement();
        if (c > 1)
            notEmpty.signal();
    } finally {
        takeLock.unlock();
    }

    // 注意,c 是调用 getAndDecrement 返回的,如果 if 成立,
    // 表明前面的 count == capacity ,有put线程在等待,可以添加新元素,所以唤醒 添加线程。
    if (c == capacity)
        signalNotFull();
    return x;
}

enqueue,dequeue

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

// 在持有 putLock 锁下执行
private void enqueue(Node<E> node) {
    // assert putLock.isHeldByCurrentThread();
    // assert last.next == null;
    //把node作为最后一个节点,更新last.next引用
    last = last.next = node;
}


// 在持有 takeLock 锁下执行
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;
}

signal

    private void signalNotEmpty() {
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();
        try {
            notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
    }

    private void signalNotFull() {
        final ReentrantLock putLock = this.putLock;
        putLock.lock();
        try {
            notFull.signal();
        } finally {
            putLock.unlock();
        }
    }

fullLock

Some of these methods must also take and put a lock acquisition

When the need to use two locks, lock release order and order is very important: You must be locked in a fixed order, then the order of the lock to release the lock reverse order.

    void fullyLock() {
        putLock.lock();
        takeLock.lock();
    }

    void fullyUnlock() {
        takeLock.unlock();
        putLock.unlock();
    }

    public boolean remove(Object o) {
        if (o == null) return false;
        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();
        }
    }

    void unlink(Node<E> p, Node<E> trail) {
        // assert isFullyLocked();
        // p.next is not changed, to allow iterators that are
        // traversing p to maintain their weak-consistency guarantee.
        p.item = null;
        trail.next = p.next;
        if (last == p)
            last = trail;
        if (count.getAndDecrement() == capacity)
            notFull.signal();
    }

note

  • Why always after each put () wake up other threads put or take () to wake up other threads take

Because signalNotEmpty (), signalNotFull () method only wake a thread, if there are multiple threads waiting, it will lead the remaining thread has been suspended.

  • Why signalNotEmpty (), signalNotFull () does not wake multiple threads

After assuming that only one space, the N wakeup put threads, thread take only performs a put operation, the N-1 other threads just wake, but also suspended, affect performance

 

reference

 

 

 

Guess you like

Origin blog.csdn.net/demon7552003/article/details/90725866