1. 并发List
Vector和CopyOnWriteArrayList,是两个并发list的实现
CopyOnWriteArrayList:实现原理是利用了对象的不变性,当没有进行写操作时,不加锁,当有写操作时先复制一份对象副本,然后进行修改,最后保存时加锁,通过以上方式减少锁竞争,从原理来看比较适合读多写少场景。
Vector:读和写都试用了synchorinized关键字,高并发读写下性能较差。但是写多读少场景有时比CopyOnWriteArrayList性能更好
我们先来测试一下读多写少场景
public class ConcurrentTest {
//线程池线程数量
private static Integer threadNum = 50;
//并发任务量
private static Integer taskNum = 200;
//数据量
private static Integer dataNum = 200;
static CopyOnWriteArrayList<Integer> concurrentList = new CopyOnWriteArrayList<>();
//private static Vector<Integer> concurrentList = new Vector<>();
static {
for (int i = 0; i < dataNum; i++) {
concurrentList.add(i);
}
}
static class MyArray extends Thread {
@Override
public void run() {
for (int i = 0; i < dataNum; i++) {
concurrentList.get(i);
}
}
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
ThreadPoolExecutor poolExecutor = new CounterThreadPool(threadNum, threadNum, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
CounterThreadPool.startTime = start;
for (int i = 0; i < taskNum; i++) {
poolExecutor.submit(new MyArray());
}
poolExecutor.shutdown();
}
static class CounterThreadPool extends ThreadPoolExecutor {
public static long startTime = 0;
private AtomicInteger size = new AtomicInteger(0);
public CounterThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
int i = size.incrementAndGet();
if (i == taskNum) {
long end = System.currentTimeMillis();
System.out.println("耗时:====:" + (end - startTime));
}
}
}
}
切换容器,运行两次结果
CopyOnWriteArrayList:7ms
Vector:15ms
可以看到高并发读场景,CopyOnWriteArrayList性能更好,性能提高一倍。
修改get方法改为add,测试一下写性能:
CopyOnWriteArrayList:967ms
Vector:14ms
高并发写场景,Vector性能更好,性能高一个数量级。
2. 并发Set
和List一样,并发set也有一个CopyOnWriteArraySet容器,它的内部完全依赖CopyOnWriteArrayList,因此性能和CopyOnWriteArrayList类似 是和并发读
并发写场景可以使用Collections.synchronizedSet(new HashSet<>());获得一个同步容器
经过实际测试,并发写性能CopyOnWriteArraySet非常差相差两个数量级,建议使用synchronizedSet
由于Set接口没有get方法,经实际测试,contains方法两者差别并不大。
测试代码和上面的类似大家自己试一下就好了,这里就不贴了
3. 并发Map
由于Map使用率非常高Doug Lea大神专门为大家写了一个并发Map容器----ConcurrentHashMap
ConcurrentHashMap<>(),无论是并发读性能还是并发写性能,都比synchronizedMap高一倍。所以并发读写都推荐大家使用ConcurrentHashMap。另外还有一个古老的HashTable由于会锁整张表性能较差所以一般不用。
测试代码和上面的类似大家自己试一下就好了,这里就不贴了
关于其原理大家可以看这一篇文章:Java并发编程笔记之ConcurrentHashMap原理探究
4. 并发Queue
在并发队列上JDK提供了两套实现:ConcurrentLinkedQueue为代表的高性能队列。一个是以BlockingQueue为代表的阻塞队列,
单纯从高性能读写上来说ConcurrentLinkedQueue性能要高于BlockingQueue,但是BlockingQueue的设计主要功能并不是高性能,而是在于高性能的同时简化多线程间的数据共享问题,所以线程池的参数用的是BlockingQueue而不是ConcurrentLinkedQueue。
此外还有,Disruptor是一个开源的Java框架,它被设计用于在生产者—消费者(producer-consumer problem,简称PCP)问题上获得尽量高的吞吐量(TPS)和尽量低的延迟。Disruptor是LMAX在线交易平台的关键组成部分,LMAX平台使用该框架对订单处理速度能达到600万TPS,除金融领域之外,其他一般的应用中都可以用到Disruptor,它可以带来显著的性能提升。其实Disruptor与其说是一个框架,不如说是一种设计思路,这个设计思路对于存在“并发、缓冲区、生产者—消费者模型、事务处理”这些元素的程序来说,Disruptor提出了一种大幅提升性能(TPS)的方案。
Disruptor比BlockingQueue性能更好,storm框架使用的就是Disruptor
5. 并发Deque
LinkedBlockingDeque是一种双端队列(Double-Ended Queue),它的内部维护着一个前驱节点一个后驱节点,没有进行读写锁的分离,性能较差,主要是体统了一些双端队列功能。