java中的阻塞和非阻塞队列

java中的阻塞和非阻塞队列

实现一个队列的线程安全,有两种方式:
	1)使用阻塞队列,即出队和入队共用一把锁或者各自使用一把锁来实现
	2)非阻塞队列:可以利用循环CAS的方式实现

java中的阻塞队列

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

阻塞队列的实现原理

使用等待通知机制

非阻塞队列

concurrentLinkedQueue:利用循环CAS来实现线程安全。它是一个基于链表的无界线程安全队列。
  1. ConcurrentLinkedQueue的结构
    由head节点和tail节点构成,默认情况下head节点的元素为空,tail节点等于head节点。
  2. 节点入队:定位出尾节点,然后利用CAS算法将尾节点的下一个节点指向新加节点(tail节点不一定是尾节点,可以通过tail节点去定位尾节点)
  3. 节点出队:首先获取头节点的元素,然后判断头节点元素是否为空,如果为空,表示另外一个线程已经进行了一次出队操作将该节点的元素取走;如果不为空,则使用CAS的方式将头节点的引用设置成null,如果CAS成功,则直接返回头节点的元素,如果不成功,表示另外一个线程已经进行了一次出队操作更新了head节点,导致元素发生了变化,需要重新获取头节点。
  4. peek()方法后head会指向第一个具有非空元素的节点。
  5. size()方法用来获取当前队列的元素个数,但是因为没加锁,所以可能不精确,建议用isEmpty()

不设置tail节点为尾节点的好处(头节点类似)

  • 让tail节点永远作为队列的尾节点,每次都需要使用循环CAS更新tail节点。减少CAS更新tail节点的次数,就能提高入队的效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值