LinkedBlockingQueue----------干戈的想法

LinkedBlockingQueue

上次看了数组队列,这次来看链表形式的队列。

温馨提示:这个链表是单向的。

先来了解下类的成员

成员

	// capacity表示队列的大小
    private final int capacity;
    // count表示队列里元素的多少,AtomicInteger 具有原子性,可保证线程安全性
    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();

构造方法

在初始化队列的时候,如果用户没有设置队列的大小,那么默认队列的大小为Integer.MAX_VALUE = 2147483647‬。

初始化了头结点和尾节点。

    public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node<E>(null);
    }
    public LinkedBlockingQueue() {
    	this(Integer.MAX_VALUE);
    }

Node节点

可以看出来,队列是单向的

    static class Node<E> {
        E item;
        Node<E> next;

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

添加一个元素

put(E e)方法

添加一个元素,将新节点加到队列的后面。在这个类里面所有的添加方法都是从后面加。

当队列满了之后,将不会继续添加元素。那些继续添加元素的线程将会被阻塞。

要想唤醒这些被阻塞的线程,有两种路径。
一种是队列里的元素被删除,会唤醒。
一种是某一个添加元素的线程添加的时候发现队列未满,他会唤醒那些被阻塞的线程。

    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) {
                notFull.await();
            }
            // 如果没满,那么将节点加到末尾
            enqueue(node);
            // 这个方法会先得到当前值赋值给c,然后才将当前值加一
            c = count.getAndIncrement();
            // 给c + 1,才是队列里元素的个数,若不满,就唤醒被阻塞的线程
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            putLock.unlock();	// 开锁
        }
        // 我们设想这样一个场景,当队列里才加入第一个元素此时c的值为0,但
        // 是这就表示了此时队列里是有元素的,此时他会唤醒那些因为删除的时
        // 候发现队列为空而被阻塞的线程。
        if (c == 0)
            signalNotEmpty();
    }

takeLock对应notEmpty
putLock对应notFull
不论是唤醒还是阻塞,都要在各自的锁下去完成。

    private void signalNotEmpty() {
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();
        try {
            notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
    }
    private void enqueue(Node<E> node) {
        last = last.next = node;
    }

offer(E e)

这个方法也是添加元素的方法,它与上面不同的地方就是当队列满了之后他不会阻塞线程,而是返回false,当然还是不会再向队列添加元素了。

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

删除一个元素

删除元素有两类,一类是只删除头节点,一类是删除指定元素。
首先我们来看只删除头节点的

take()

删除的时候从头节点开始删,如果队列空了,那么删除节点的线程就会被堵塞。与上面添加元素的很相似。

等待队列重新添加元素唤醒被阻塞的线程
等待其他删除线程删除完之后发现队列里还有元素,那么就唤醒被阻塞的线程。

他还会去唤醒那些因为队列已满而被阻塞的线程
假设队列的容量为5,此时队列已满,然后有一个线程删除了队列中的一个元素。首先得到count的值赋值给c然后将count的值减一。
此时c的值为5
count的值为4
所以当后面判断c的值为5,也就是队列的容量值。就说明此时队列里有空余的位置,所以他会唤醒其他因队列满而被阻塞的线程。

    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();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }
    private void signalNotFull() {
        final ReentrantLock putLock = this.putLock;
        putLock.lock();
        try {
            notFull.signal();
        } finally {
            putLock.unlock();
        }
    }
// 删除的是头结点
    private E dequeue() {
        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;
    }

poll()

这个删除的方法与上面不同的地方就是不阻塞线程,如果删除时队列为空,那么直接返回null, 其他都一样。

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

remove(Object o)

这个删除方法有点特殊,和上面那些不一样。他会删除指定元素。
那么此时如果添加删除用不同的锁是线程不安全的。

设想一下,假如我有两个线程。线程1要添加一个元素到末尾;线程2要删除一个指定元素,而这个指定元素就放在此时队列的末尾。

线程1执行到取出末尾元素后时间片段就到了,线程2开始删最后一个元素,删完以后线程2结束。线程1开始就着刚才拿出来的已经删除的节点将自己连接在后面,还将自己设置为尾节点,这不就错了么。

所以我们再进行这个删除的方法的时候,两把锁同时都要上锁。某个线程再删除的时候就不能再进行添加了。

如果删除时队列为空或者没有这个元素,那么方法返回false。

    public boolean remove(Object o) {
        if (o == null) return false;
        // 这是两把锁都上锁了。
        fullyLock();
        try {
        // 遍历列表找到要删除的节点,将要删除的节点和他前面的节点传递给unlink方法。
            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) {
        p.item = null;
        trail.next = p.next;
        if (last == p)
            last = trail;
        if (count.getAndDecrement() == capacity)
            notFull.signal();
    }
    void fullyLock() {
        putLock.lock();
        takeLock.lock();
    }
    void fullyUnlock() {
        takeLock.unlock();
        putLock.unlock();
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值