java中的阻塞和非阻塞队列
实现一个队列的线程安全,有两种方式:
1)使用阻塞队列,即出队和入队共用一把锁或者各自使用一把锁来实现
2)非阻塞队列:可以利用循环CAS的方式实现
java中的阻塞队列
阻塞队列是一个支持两个附加操作的队列,即支持阻塞的插入和移除。
1. 阻塞的插入:当前队列已经满了的时候,队列会阻塞插入元素的线程,直到队列不满
2. 阻塞的移除:当前队列为空的时候,获取元素的队列会阻塞直到队列为非空。
处理方式:抛出异常,返回特殊值,一直阻塞或者超时退出。
经常用于生产者和消费者模式。
- java中的7个阻塞队列:
- ArrayBlockingQueue:由数组结构组成的有界阻塞队列
- LinkedBlockingQueue: 链表结构组成的有界阻塞队列
- PriorityBlockingQueue: 支持优先级排序的无界阻塞队列
- DelayQueue:使用优先级队列实现的无界阻塞队列
- SynchronousQueue:不存储元素的阻塞队列
- LinkedTransferQueue: 链表组成的无界阻塞队列
- LinkedBlockingDeque:链表结构组成的双向阻塞队列
- ArrayBlockingQueue
数组结构组成的有界阻塞队列,可以实现公平阻塞或者非公平阻塞(源码内部是通过可重入锁ReentrantLock实现的)
ArrayBlockingQueue queue = new ArrayBlockingQueue(1000, true); - LinkedBlockingQueue
链表结构组成的有界阻塞队列,队列的默认和最大长度是Integer.MAX_VALUE。吞吐量比ArrayBlockQueue要高。
- 静态工厂方法:Executors.newFixedThreadPool()使用了这个队列。
- PriorityBlockingQueue
支持优先级排序的无界阻塞队列,默认情况下是按照自然顺序进行升序排序的。可以自定义类实现CompareTo()方法来指定元素排序规则或者在初始化的时候指定构造参数Comparator来对元素进行排序。但是不能保证同优先级元素的顺序。 - DelayQueue
支持延时获取元素的无界阻塞队列,队列使用PriorityQueue实现,队列中的元素必须实现了Delayed接口,在创建元素的时候可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。
- 缓存系统的设计:可以用DelayQueue保存元素的有效期,一旦从队列中提取元素时,就表示该元素的有效期到了
- 定时任务调查:使用DelayQueue保存任务和任务的定时器
- SynchronousQueue
- 不存储元素的阻塞队列,每一个put操作都必须等待一个take操作,否则不能继续添加元素。支持公平访问,但是默认是非公平的。
- 适合传递性场景,吞吐量比ArrayBlockingQueue和LInkedBlockingQueue要高。
- 静态工厂方法Executors.newCachedThreadPool使用该队列。
- LinkedTransferQueue
由链表结构构成的无界阻塞队列,主要多了tryTransfer和transfer方法。
transfer方法:如果当前有消费者正在等待接收元素,该方法就直接把生产者传入的元素传递给消费者,否则的话,会将该元素放在队列的tail节点,并等待被消费者消费了才返回。
tryTransfer方法:与tansefer不同的是,它会立即返回,即使无消费者接收该元素,也会立即返回false。 - LinkedBlockingDeque
链表结构组成的双端阻塞队列,初始化时设置其容量防止多度膨胀。适用于工作窃取模式中。
阻塞队列的实现原理
使用等待通知机制
非阻塞队列
concurrentLinkedQueue:利用循环CAS来实现线程安全。它是一个基于链表的无界线程安全队列。
- ConcurrentLinkedQueue的结构
由head节点和tail节点构成,默认情况下head节点的元素为空,tail节点等于head节点。 - 节点入队:定位出尾节点,然后利用CAS算法将尾节点的下一个节点指向新加节点(tail节点不一定是尾节点,可以通过tail节点去定位尾节点)
- 节点出队:首先获取头节点的元素,然后判断头节点元素是否为空,如果为空,表示另外一个线程已经进行了一次出队操作将该节点的元素取走;如果不为空,则使用CAS的方式将头节点的引用设置成null,如果CAS成功,则直接返回头节点的元素,如果不成功,表示另外一个线程已经进行了一次出队操作更新了head节点,导致元素发生了变化,需要重新获取头节点。
- peek()方法后head会指向第一个具有非空元素的节点。
- size()方法用来获取当前队列的元素个数,但是因为没加锁,所以可能不精确,建议用isEmpty()
不设置tail节点为尾节点的好处(头节点类似):
- 让tail节点永远作为队列的尾节点,每次都需要使用循环CAS更新tail节点。减少CAS更新tail节点的次数,就能提高入队的效率。