SynchronousQueue,LinkedBlockingQueue,ArrayBlockingQueue区别

队列

是否有界

是否缓冲

锁个数

并发性能

SynchronousQueue

缓冲为1

1

线程少 (<20) Queue长度短 (<30) , 使用SynchronousQueue表现很稳定,而且在20个线程之内不管Queue长短,SynchronousQueue性能表现是最好的,SynchronousQueueQueue长短没有关系)

ArrayBlcokingQueue

1

一般不用,并且在插入和删除元素时会产生额外的对象

LinkedBlockingQueue

2(生产者锁和消费者锁)

LinkedBlockingQueue性能表现远超ArrayBlcokingQueue,不管线程多少,不管Queue长短,LinkedBlockingQueue都胜过ArrayBlockingQueue,线程多(>20),Queue长度长(>30),使用LinkedBlockingQueue



SynchronousQueue

        SynchronousQueue是无界的,是一种无缓冲的等待队列,但是由于该Queue本身的特性,在某次添加元素后必须等待其他线程取走后才能继续添加;可以认为SynchronousQueue是一个缓存值为1的阻塞队列,但是 isEmpty()方法永远返回是trueremainingCapacity() 方法永远返回是0,remove()和removeAll() 方法永远返回是false,iterator()方法永远返回空,peek()方法永远返回null

        声明一个SynchronousQueue有两种不同的方式,它们之间有着不太一样的行为。公平模式和非公平模式的区别:如果采用公平模式:SynchronousQueue会采用公平锁,并配合一个FIFO队列来阻塞多余的生产者和消费者,从而体系整体的公平策略;但如果是非公平模式(SynchronousQueue默认):SynchronousQueue采用非公平锁,同时配合一个LIFO队列来管理多余的生产者和消费者,而后一种模式,如果生产者和消费者的处理速度有差距,则很容易出现饥渴的情况,即可能有某些生产者或者是消费者的数据永远都得不到处理。

LinkedBlockingQueue

        LinkedBlockingQueue是无界的,是一个无界缓存的等待队列。

        基于链表的阻塞队列,内部维持着一个数据缓冲队列(该队列由链表构成)。当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。

        LinkedBlockingQueue之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。

ArrayListBlockingQueue

         ArrayListBlockingQueue是有界的,是一个有界缓存的等待队列。
         基于数组的阻塞队列,同LinkedBlockingQueue类似,内部维持着一个定长数据缓冲队列(该队列由数组构成)。ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。
         ArrayBlockingQueue在生产者放入数据和消费者获取数据,都是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于LinkedBlockingQueue;按照实现原理来分析,ArrayBlockingQueue完全可以采用分离锁,从而实现生产者和消费者操作的完全并行运行。Doug Lea之所以没这样去做,也许是因为ArrayBlockingQueue的数据写入和获取操作已经足够轻巧,以至于引入独立的锁机制,除了给代码带来额外的复杂性外,其在性能上完全占不到任何便宜。 ArrayBlockingQueue和LinkedBlockingQueue间还有一个明显的不同之处在于,前者在插入或删除元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的Node对象。这在长时间内需要高效并发地处理大批量数据的系统中,其对于GC的影响还是存在一定的区别。

         ArrayBlockingQueue和LinkedBlockingQueue是两个最普通、最常用的阻塞队列,一般情况下,处理多线程间的生产者消费者问题,使用这两个类足以。


对于容器类在并发环境下的比较,一是是否线程安全,二是并发性能如何。BlockingQueue的实现都是线程安全的,所以只能比比它们的并发性能了。在不同的应用场景中,对容器的使用情况不同,有的读取操作多修改写入操作少,有的修改写入操作多,这对容器的性能会造成不同的影响。但对于Queue的使用,个人认为是比较一致的,简单点就是put和get,不会修改某个元素的内容再被读取,也很少只读取的操作,那是不是有最佳实践了?


代码比较长,我还是放在后面,先说结论。没有想到的是LinkedBlockingQueue性能表现远超ArrayBlcokingQueue,不管线程多少,不管Queue长短,LinkedBlockingQueue都胜过ArrayBlockingQueue。SynchronousQueue表现很稳定,而且在20个线程之内不管Queue长短,SynchronousQueue性能表现是最好的,(其实SynchronousQueue跟Queue长短没有关系),如果Queue的capability只能是1,那么毫无疑问选择SynchronousQueue,这也是设计SynchronousQueue的目的吧。但大家也可以看到当超过1000个线程时,SynchronousQueue性能就直线下降了,只有最高峰的一半左右,而且当Queue大于30时,LinkedBlockingQueue性能就超过SynchronousQueue。


结论:

  • 线程多(>20),Queue长度长(>30),使用LinkedBlockingQueue

  • 线程少 (<20) ,Queue长度短 (<30) , 使用SynchronousQueue

当然,使用SynchronousQueue的时候不要忘记应用的扩展,如果将来需要进行扩展还是选择LinkedBlockingQueue好,尽量把SynchronousQueue限制在特殊场景中使用。

  • 少用ArrayBlcokingQueue,似乎没找到它的好处,高手给给建议吧!


最后看看测试代码和结果:(Win7 64bit + JDK7 + CPU4 + 4GB)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.SynchronousQueue;
public class TestSynchronousQueue {
     private static int THREAD_NUM;
     private static int N = 1000000 ;
     private static ExecutorService executor;
     public static void main(String[] args) throws Exception {                   
         System.out.println( "Producer\tConsumer\tcapacity \t LinkedBlockingQueue \t ArrayBlockingQueue \t SynchronousQueue" );
                            
         for ( int j = 0 ; j< 10 ; j++){
             THREAD_NUM = ( int ) Math.pow( 2 , j);
             executor = Executors.newFixedThreadPool(THREAD_NUM * 2 );
                                
             for ( int i = 0 ; i < 10 ; i++) {
                 int length = (i == 0 ) ? 1 : i * 10 ;
                 System.out.print(THREAD_NUM + "\t\t" );
                 System.out.print(THREAD_NUM + "\t\t" );
                 System.out.print(length + "\t\t" );
                 System.out.print(doTest2( new LinkedBlockingQueue<Integer>(length), N) + "/s\t\t\t" );
                 System.out.print(doTest2( new ArrayBlockingQueue<Integer>(length), N) + "/s\t\t\t" );
                 System.out.print(doTest2( new SynchronousQueue<Integer>(), N) + "/s" );
                 System.out.println();
             }
                                
             executor.shutdown();
         }
     }
                        
     private static class Producer implements Runnable{
         int n;
         BlockingQueue<Integer> q;
                            
         public Producer( int initN, BlockingQueue<Integer> initQ){
             n = initN;
             q = initQ;
         }
                            
         public void run() {
             for ( int i = 0 ; i < n; i++)
                 try {
                     q.put(i);
                 } catch (InterruptedException ex) {
                 }
         }
     }
                        
     private static class Consumer implements Callable<Long>{
         int n;
         BlockingQueue<Integer> q;
                            
         public Consumer( int initN, BlockingQueue<Integer> initQ){
             n = initN;
             q = initQ;
         }
                            
         public Long call() {
             long sum = 0 ;
             for ( int i = 0 ; i < n; i++)
                 try {
                     sum += q.take();
                 } catch (InterruptedException ex) {
                 }
             return sum;
         }
     }
                        
     private static long doTest2( final BlockingQueue<Integer> q, final int n)
             throws Exception {
         CompletionService<Long> completionServ = new ExecutorCompletionService<Long>(executor);
                            
         long t = System.nanoTime();
         for ( int i= 0 ; i<THREAD_NUM; i++){
             executor.submit( new Producer(n/THREAD_NUM, q));
         }   
         for ( int i= 0 ; i<THREAD_NUM; i++){
             completionServ.submit( new Consumer(n/THREAD_NUM, q));
         }
                            
         for ( int i= 0 ; i<THREAD_NUM; i++){
             completionServ.take().get();
         }   
                            
         t = System.nanoTime() - t;
         return ( long ) ( 1000000000.0 * N / t); // Throughput, items/sec
     }


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值