java并发队列之非阻塞队列ConcurrentLinkedQueue(七)

java并发队列之非阻塞队列ConcurrentLinkedQueue(七)

ConcurrentLinkedQueue是一个非阻塞,无界的高并发队列.底层数据结构使用单链表来实现,出队和入队操作使用CAS来实现线程安全.

从图中可以看出非阻塞队列和阻塞队列非常像,只是非阻塞队列并未实现BlackingQueue接口.

实战

public class ConcurrentLinkedQueueDemo {

    public static void main(String[] args) {

       final ConcurrentLinkedQueue<String> deque = new ConcurrentLinkedQueue<>();


        final
        Runnable producerRunnable = new Runnable() {
            int i = 0;

            public void run() {
                while (true) {
                    i++;
                    try {
                        System.out.println("我生产了一个===" + i);
                        deque.add(i + "dddd");
                        //为了凸显非阻塞队列的特性,这里时间设置长一点,而take的等待时间设置短一点,来看看poll是非阻塞的.
                        //两边的时间也可以对调来检验put是非阻塞的.
                        //注意和前面的阻塞队列做对比哦.
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };

        Runnable customerRunnable = new Runnable() {
            public void run() {
                while (true) {
                    try {
                    	
                        System.out.println("我消费了一个===" + deque.poll());
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };

        Thread thread1 = new Thread(producerRunnable);
        thread1.start();

        Thread thread2 = new Thread(customerRunnable);
        thread2.start();

    }

}
输出结果:
我生产了一个===1
我消费了一个===1dddd
我消费了一个===null
我生产了一个===2
我消费了一个===2dddd
我消费了一个===null
我生产了一个===3
我消费了一个===3dddd
我消费了一个===null
我生产了一个===4
我消费了一个===4dddd
我消费了一个===null
我生产了一个===5
我消费了一个===5dddd
我消费了一个===null
我生产了一个===6
我消费了一个===6dddd
我消费了一个===null

注意结果和前面的阻塞队列做对比哦.
结果解说:可以看到非阻塞队列并没有take方法(阻塞).在阻塞队列中,调用take时如果队列里面没有元素则会阻塞知道队列里面有元素.而非阻塞第一是没有take阻塞方法,第二是在poll获取元素时,发现没有元素并不会去阻塞线程,而是直接返回null.

ConcurrentLinkedQueue特性

  • 没有实现BlockingQueue,也就没有了阻塞方法put,take.
  • 队列尾添加元素,队列头获取元素.
  • 没有像阻塞队列里面使用Lock锁来保证安全性.使用CAS操作保证安全性,性能较高.
  • 内部使用单链表结构,无界,无阻塞.

源码解析

入栈操作offer

	private transient volatile Node<E> head;

    private transient volatile Node<E> tail;

	//初始化时,创建了一个null节点,头尾都指向它
    public ConcurrentLinkedQueue() {
        head = tail = new Node<E>(null);
    }
    
public boolean offer(E e) {
        checkNotNull(e);
        //构造Node 节点,在构造函数内部调用unsafe.putObject来添加元素
        final Node<E> newNode = new Node<E>(e);
		 
        for (Node<E> t = tail, p = t;;) {
            Node<E> q = p.next;
            if (q == null) {
                // 如果尾节点为null则执行新增插入动作.
                //cas操作
                if (p.casNext(null, newNode)) { 
                    if (p != t) // hop two nodes at a time
                        casTail(t, newNode);  // Failure is OK.
                    return true;
                }
                // Lost CAS race to another thread; re-read next
            }
            else if (p == q)
                //防止多线程操作时,另外一个节点poll操作移除之后,head变成自引用.
                //重新找新的head
                p = (t != (t = tail)) ? t : head;
            else
                //寻找尾节点
                p = (p != t && t != (t = tail)) ? t : q;
        }
}

poll弹出队列操作

public E poll() {
        restartFromHead:
        for (;;) {
            for (Node<E> h = head, p = h, q;;) {
                E item = p.item;

                if (item != null && p.casItem(item, null)) {
                    //出队列后,更新头节点往下移动.等同于删除一个元素
                    if (p != h) // hop two nodes at a time
                        updateHead(h, ((q = p.next) != null) ? q : p);
                    return item;
                }
                else if ((q = p.next) == null) {
                    updateHead(h, p);
                    return null;
                }
                else if (p == q)
                    continue restartFromHead;
                else
                    p = q;
            }
        }
    }

总结:ConcurrentLinkedQu eue 的底层使用单向链表数据结构来保存队列元素,每个元素被包装成一个Node 节点。队列是靠头、尾节点来维护的,创建队列时头、尾节点指向-个item 为null 的哨兵节点。第一次执行peek 或者自rst 操作时会把head 指向第一个真正的队列元素。由于使用非阻塞CAS 算法,没有加锁,所以在计算size 时有可能进行了offer 、poll 或者remove 操作, 导致计算的元素个数不精确,所以在井发情况下size 函数不是很有用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值