(原创)JAVA阻塞队列LinkedBlockingQueue 以及非阻塞队列ConcurrentLinkedQueue 的区别

阻塞队列:线程安全

按 FIFO(先进先出)排序元素。队列的头部 是在队列中时间最长的元素。队列的尾部 是在队列中时间最短的元素。新元素插入到队列的尾部,并且队列检索操作会获得位于队列头部的元素。链接队列的吞吐量通常要高于基于数组的队列,但是在大多数并发应用程序中,其可预知的性能要低。

注意:

1、必须要使用take()方法在获取的时候达成阻塞结果
2、使用poll()方法将产生非阻塞效果


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

 

public class BlockingDeque {
    //阻塞队列,FIFO
    private static LinkedBlockingQueue<Integer> concurrentLinkedQueue = new LinkedBlockingQueue<Integer>(); 

          
 public static void main(String[] args) {  
     ExecutorService executorService = Executors.newFixedThreadPool(2);  

     executorService.submit(new Producer("producer1"));  
     executorService.submit(new Producer("producer2"));  
     executorService.submit(new Producer("producer3"));  
     executorService.submit(new Consumer("consumer1"));  
     executorService.submit(new Consumer("consumer2"));  
     executorService.submit(new Consumer("consumer3"));  

 }  

 static class Producer implements Runnable {  
     private String name;  

     public Producer(String name) {  
         this.name = name;  
     }  

     public void run() {  
         for (int i = 1; i < 10; ++i) {  
             System.out.println(name+ "  生产: " + i);  
             //concurrentLinkedQueue.add(i);  
             try {
                concurrentLinkedQueue.put(i);
                Thread.sleep(200); //模拟慢速的生产,产生阻塞的效果
            } catch (InterruptedException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }
             
         }  
     }  
 }  

 static class Consumer implements Runnable {  
     private String name;  

     public Consumer(String name) {  
         this.name = name;  
     }  
     public void run() {  
         for (int i = 1; i < 10; ++i) {  
             try {          
                    //必须要使用take()方法在获取的时候阻塞
                      System.out.println(name+"消费: " +  concurrentLinkedQueue.take());  
                      //使用poll()方法 将产生非阻塞效果
                      //System.out.println(name+"消费: " +  concurrentLinkedQueue.poll());  
                     
                     //还有一个超时的用法,队列空时,指定阻塞时间后返回,不会一直阻塞
                     //但有一个疑问,既然可以不阻塞,为啥还叫阻塞队列?
                    //System.out.println(name+" Consumer " +  concurrentLinkedQueue.poll(300, TimeUnit.MILLISECONDS));                    
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }  

         }  
     }  
 }  
}

 

 

 

非阻塞队列

基于链接节点的、无界的、线程安全。此队列按照 FIFO(先进先出)原则对元素进行排序。队列的头部 是队列中时间最长的元素。队列的尾部 是队列中时间最短的元素。新的元素插入到队列的尾部,队列检索操作从队列头部获得元素。当许多线程共享访问一个公共 collection 时,ConcurrentLinkedQueue 是一个恰当的选择。此队列不允许 null 元素。

 

例子

import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;


public class NoBlockQueue {  
       private static ConcurrentLinkedQueue<Integer> concurrentLinkedQueue = new ConcurrentLinkedQueue<Integer>();   
          
    public static void main(String[] args) {  
        ExecutorService executorService = Executors.newFixedThreadPool(2);  

        executorService.submit(new Producer("producer1"));  
        executorService.submit(new Producer("producer2"));  
        executorService.submit(new Producer("producer3"));  
        executorService.submit(new Consumer("consumer1"));  
        executorService.submit(new Consumer("consumer2"));  
        executorService.submit(new Consumer("consumer3"));  

    }  
  
    static class Producer implements Runnable {  
        private String name;  
  
        public Producer(String name) {  
            this.name = name;  
        }  
  
        public void run() {  
            for (int i = 1; i < 10; ++i) {  
                System.out.println(name+ " start producer " + i);  
                concurrentLinkedQueue.add(i);  
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                //System.out.println(name+"end producer " + i);  
            }  
        }  
    }  
  
    static class Consumer implements Runnable {  
        private String name;  
  
        public Consumer(String name) {  
            this.name = name;  
        }  
        public void run() {  
            for (int i = 1; i < 10; ++i) {  
                try {
 
                    System.out.println(name+" Consumer " +  concurrentLinkedQueue.poll());

                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }  
//                System.out.println();  
//                System.out.println(name+" end Consumer " + i);  
            }  
        }  
    }  
} 

 

在并发编程中,一般推荐使用阻塞队列,这样实现可以尽量地避免程序出现意外的错误。阻塞队列使用最经典的场景就是socket客户端数据的读取和解析,读取数据的线程不断将数据放入队列,然后解析线程不断从队列取数据解析。还有其他类似的场景,只要符合生产者-消费者模型的都可以使用阻塞队列。

 

使用非阻塞队列,虽然能即时返回结果(消费结果),但必须自行编码解决返回为空的情况处理(以及消费重试等问题)。

另外他们都是线程安全的,不用考虑线程同步问题。

<think>好的,我现在要回答用户的问题,主要是关于LinkedBlockingQueue是否属于阻塞队列,以及线程池为何通常使用阻塞队列而不是非阻塞队列。我需要参考用户提供的引用内容,并结合自己的知识来组织答案。 首先,用户的问题分两部分:确认LinkedBlockingQueue是否为阻塞队列,以及线程池使用阻塞队列的原因。根据引用内容,引用[1]提到LinkedBlockingQueue阻塞队列,引用[4]也讨论了LinkedBlockingQueue作为默认线程池的阻塞队列。所以第一部分应该直接回答LinkedBlockingQueue属于阻塞队列,并解释其特性,比如基于链表、可选有界等。 接下来是线程池使用阻塞队列的原因。用户之前的回答已经详细列过阻塞队列的优势,比如资源管控、线程协作、适应任务波动等。这里需要结合用户的引用内容,比如引用[2]中提到的阻塞队列的put和take方法使用锁和条件变量,当队列满或空时线程会进入等待状态,避免CPU空转。引用[4]提到LinkedBlockingQueue作为默认选择的原因,可能包括吞吐量和灵活性。需要将这些点整合起来,说明阻塞队列如何帮助线程池管理任务和线程资源,而非阻塞队列的问题,如无法缓冲任务、导致频繁拒绝或资源耗尽。 另外,用户引用[3]提到Tomcat的线程池使用TaskQueue(继承自LinkedBlockingQueue),说明实际应用中的例子,可以引用这个例子来支持线程池使用阻塞队列的常见性。 需要注意用户可能已经了解部分内容,所以需要简明扼要,结构清晰,分点回答。同时,用户提供的引用中有一些源码分析,比如put方法使用await()导致线程进入WAITING状态,这可以用于解释阻塞队列如何工作,避免线程忙等待,提升效率。 可能还需要对比阻塞队列非阻塞队列的差异,比如非阻塞队列ConcurrentLinkedQueue队列满或空时立即返回,导致线程池无法有效处理任务,需要依赖拒绝策略,而阻塞队列能自然地进行流量控制和资源管理。 最后,总结LinkedBlockingQueue作为阻塞队列的地位,以及线程池选择它的原因,包括设计上的协同工作、资源管控和稳定性等。</think>### **LinkedBlockingQueue阻塞队列属性及线程池选择原因** --- #### **一、LinkedBlockingQueue是标准的阻塞队列** 1. **定义与特性** - **阻塞队列接口实现**: $LinkedBlockingQueue$ 是 Java 中实现了 $BlockingQueue$ 接口的典型阻塞队列,支持线程安全的**生产者-消费者模型**。其核心方法(如$put()$、$take()$)在队列满或空时自动阻塞线程,符合阻塞队列的核心特征[^1][^4]。 - **数据结构与容量**: - 基于链表实现,默认容量为$Integer.MAX\_VALUE$(无界队列),也可通过构造函数指定为有界队列。 - 高并发场景下吞吐量优于$ArrayBlockingQueue$,因采用“双锁分离”设计(生产者和消费者使用独立锁),减少竞争[^1][^3]。 2. **典型应用场景** - **线程池默认队列**: Java 标准线程池(如$Executors.newFixedThreadPool$)默认使用$LinkedBlockingQueue$,因其平衡了吞吐量和任务缓冲能力[^4]。 - **框架级使用**: 如 Tomcat 的线程池通过自定义$TaskQueue$(继承自$LinkedBlockingQueue$)管理任务,优化任务提交与执行流程[^3]。 --- #### **二、线程池选择阻塞队列的核心原因** 1. **任务缓冲与流量削峰** - **队列容量动态调控**: 阻塞队列(如$LinkedBlockingQueue$)允许线程池在任务量超过$corePoolSize$时暂存任务,避免直接触发扩容或拒绝策略。例如: ```java // 线程池任务提交逻辑(简化) if (当前线程数 < corePoolSize) 创建新线程; else if (队列未满) 任务入队; else if (线程数 < maxPoolSize) 创建新线程; else 执行拒绝策略; ``` - **防止瞬时过载**: 突发流量通过队列缓冲后,线程池可按固定速率处理任务,避免短时高负载压垮系统(如电商秒杀场景)[^4]。 2. **高效的线程协作机制** - **自动阻塞与唤醒**: - 生产者线程调用$put()$时,若队列满,则通过$ReentrantLock$和$Condition$进入等待状态(调用$await()$),释放锁并挂起线程,避免空转消耗CPU[^2]。 - 消费者线程调用$take()$时,若队列空,同样挂起等待,直到新任务到达后被唤醒[^1]。 ```java // LinkedBlockingQueue.put()方法逻辑(简化) lock.lock(); try { while (队列) notFull.await(); // 线程进入WAITING状态 enqueue(task); } finally { lock.unlock(); } ``` - **减少竞争开销**: $LinkedBlockingQueue$的“双锁分离”设计(生产者和消费者使用不同锁),显著降低高并发下的锁争用,提升吞吐量[^3]。 3. **资源稳定性与参数协同** - **防止资源耗尽**: 使用有界$LinkedBlockingQueue$可限制任务堆积量,避免无限制增长导致内存溢出(OOM)。队列满后触发扩容或拒绝策略,形成资源保护屏障。 - **动态扩缩容**: 队列容量与线程池参数($corePoolSize$, $maxPoolSize$)协同工作: - 队列未满时,优先复用核心线程; - 队列满后,扩容至$maxPoolSize$以应对峰值; - 任务减少后,超时线程自动回收[^4]。 4. **与非阻塞队列的对比劣势** - **非阻塞队列的缺陷**: - **无法缓冲任务**:如$ConcurrentLinkedQueue$(非阻塞队列)在队列满时直接返回失败,导致线程池频繁触发拒绝策略,即使系统负载仅为短暂峰值。 - **CPU资源浪费**:消费者线程需循环轮询队列(如`while (queue.isEmpty())`),导致空转消耗CPU,而阻塞队列通过挂起线程彻底避免这一问题[^2]。 - **参数失效**:非阻塞队列无法与$maxPoolSize$协同,线程池可能始终维持$corePoolSize$线程数,失去弹性扩缩容能力。 --- #### **三、总结** 1. **LinkedBlockingQueue的定位**: 它是标准的阻塞队列,通过链表实现、双锁分离设计,在高并发场景下提供高吞吐量和灵活的任务缓冲能力,是线程池的默认选择之一[^1][^3]。 2. **线程池选择阻塞队列的本质原因**: - **系统稳定性**:通过队列容量限制和阻塞机制,防止资源耗尽; - **高效协作**:自动挂起/唤醒线程,减少CPU空转; - **参数协同**:与线程池核心参数动态配合,实现弹性资源管理。 --- ### **相关问题** 1. 如何根据业务场景选择$LinkedBlockingQueue$和$ArrayBlockingQueue$? 2. 为什么$SynchronousQueue$适合“直接传递”任务场景? 3. 如何监控阻塞队列长度以优化线程池性能?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值