LinkedBlockingQueue 源码分析双锁算法

198 阅读1分钟

初始化LinkedBlockingQueue

public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);
}

初始化LinkedBlockingQueue

public LinkedBlockingQueue(int capacity) {
    if (capacity <= 0) throw new IllegalArgumentException();
    this.capacity = capacity;
    // 指向空的节点 方便操作链表
    last = head = new Node<E>(null);
}

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();
            if (c + 1 < capacity)
                notFull.signal();
        }
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
    return c >= 0;
}

enqueue 入队 尾插法

private void enqueue(Node<E> node) {
    // assert putLock.isHeldByCurrentThread();
    // assert last.next == null;
    last = last.next = node;
}

dequeue 出队

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

总结

而双锁算法的巧妙之处在于设定了当集合中只有一个元素时认定集合为空。 在这种设定下:

队尾入队时只需要修改last指针和上一队尾元素的next指针。
队首出队时只需要修改head指针和准备出队元素的next指针。
那可不可能存在入队时的上一队尾元素和准备出队的元素是同一个元素?这个可以在理论上进行推断,由于集合至少会有2个元素时才会出队,则假设准备出队时,队首元素为A, A.next =B。则出队时只会修改A元素的next指针,入队只会修改B元素或者是B的下游元素的next指针。因此入队和出队不会操作同一个元素的next指针。