由于2采用读写锁的形式对读写进行控制,可能会在锁的获取与释放上损失一定的性能。所以当有多个消费者时多用1。
而对于2,我们在其源码中可以看到,获取队首元素有take与poll方法,这两者的最本质区别在于,当队列为空时take线程会被阻塞,调用wait()方法释放其所占有的资源。当有新元素入队时会被notify,但是对于poll,若队列为空,会直接返回null,所以在多线程中,如果消费者速度大于生产者速度,会导致队列经常为空,这时如果不在poll的返回值为空时进行必要的处理,会导致线程空转,最坏情况下会导致cpu使用率飙升。我们可以采用锁的形式在线程内部主动wait(),而在入队时notify或者notifyall来参照take方法,防止大量的线程空转。
对于1,由于其内部没有类似于2的take方法,除了poll与peek之外,没有提供别的获取元素的方法,这两者的区别在于是否会弹出队首元素。但是这时如果消费速度大于生产速度,同样会产生上面的问题,这时我们就必须采用显式的方法加锁调用wait()方法,防止cpu等资源的浪费。提高并发性能。
通过入队、出队一百万次进行性能比较
1、生产者10个,消费者100个(进队效率,后续会给出出队效率)
take | poll+wait() | poll | |
---|---|---|---|
LinkedBlockingQueue | 3250 / 20% | 1900 / 28% | 至少几分钟 / 100% |
ConcurrentLinkedQueue | -- | 1890 / 24% | 7400 / 75% |
对比以上数据可以得出,
1、对于LinkedBlockingQueue,take方法虽然在内部实现了加锁wait(),但是由于其他的开销,导致性能相比于poll+wait()有所下降。但是如果采用poll方法,那么由于大量的线程存在空转的情况,导致争用处理机,导致性能急剧下降。
2、对于ConcurrentLinkedQueue,由于内部采用CAS保证并发安全,在采用poll+wait()时相比前者有所提升,但是不是很明显,但是对于poll方式,由于去除了锁的开销,同时虽然相比于其自身的poll+wait()方式性能下降不少,但是相对于LinkedBlockingQueue,性能提升相当明显。