4.1 集合包
ArrayList, LinkedList,Vector,TreeSet,HashMap,TreeMap 等,无需多说
4.2 并发包 (java.util.concurrent)
4.2.1 ConcurrentHashMap
线程安全的 HashMap 实现,主要原理是将集合分成多个段 ( 默认 16 个 ) ,分段加锁,实现高并发。
据作者的测试结果,在线程数较少时,无论元素数量多少, ConcurrentHashMap 带来的性能提升不明显,但在线程数为 50 以上时, ConcurrentHashMap 在增加和删除时性能提升了一倍左右,查找时性能提升了 10 倍左右。
因而,在并发场景, ConcurrentHashMap 较之 HashMap 是更好的选择
4.2.2 CopyOnWriteArrayList
线程安全的 ArrayList, 基于 ReentrantLock 保证增加元素和删除元素动作的互斥,在读操作时无锁。
据作者的测试结果,随着元素数量和线程数量增加, CopyOnWriteArrayList 在增加元素和删除元素时性能下降明显,比 ArrayList 更低。但在查找元素上却比 ArrayList 好很多(大于 10 倍)。
因此,在读多写少的并发场景, CopyOnWriteArrayList 较之 ArrayList 是更好的选择。
4.2.3 CopyOnWriteArraySet
基于 CopyOnWriteArrayList 实现,唯一不同的是每次 add 时调用的是 addIfAbsent 方法,此方法需遍历当前 Object 数组,检查待增加的元素是否已存在,已存在则直接返回。故性能略低于 CopyOnWriteArrayList
4.2.4 ArrayBlockingQueue
一个基于固定大小数组、 ReentrantLock 以及 Condition 实现的可阻塞的先进先出的 Queue.
类似的还有过 LinkedBlockingQueue, 对 put 和 offer 采用一把锁,对 take 和 poll 采用另一把锁,避免了读写时互相竞争锁的现象,因此,在高并发读写操作都多的情况下,性能会较 ArrayBlockingQueue 好很多
4.2.5 AtomicInteger
一个支持原子操作的 Integer 类,在没有 AtomicInteger 前,要实现一个按顺序获取的 ID ,就必须在每次获取时进行加锁操作,以避免出现并发时获取到同样 ID 的现象。借助 AtomicInteger ,则可以更简易地实现,如下:
Private static AtomicInteger counter = new AtomicInteger();
Public static int getNextId() {
Return counter.incrementAndGet();
}
incrementAndGet 方法基于 CPU 的 CAS 原语实现,基于 CAS 的操作可以认为是无阻塞的,因此性能好于同步锁的方式。
对于使用 JDK5 以上的版本时,应尽量使用 atomic 的类,除 AtomicInteger 外,还有 AtomicLong 、 AtomicBoolean 、 AtomicReference 等。
4.2.6 ThreadPoolExecutor
一个高效的、支持并发的线程池,要想用好这个线程池,需要合理配置 corePoolSize, 最大线程数、任务缓冲的队列,以及线程池满时的处理策略。常见的需要有如下两种
1. 高性能
如果希望高性能地执行 Runnable 任务,即当线程池中的线程数尚未达到最大个数,则立刻交给线程执行或在最大线程数量的保护下创建线程来执行,可选的方式为使用 SynchronousQueue 作为任务的队列。
2. 缓冲执行
如果希望 Runnable 任务尽量被 corePoolSize 范围的线程执行掉,可选的方式为使用 ArrayBlockingQueue 或 LinkedBlockQueue 作为任务缓冲的队列。这样,当线程数等于或超过 corePoolSize 后,会先加入缓冲队列中,而不是直接交由线程执行。
4.2.7 Executors
提供一些方便创建 ThreadPoolExecutor 的方式
1. newFixedThreadPool(int)
创建固定大小的线程池 keepAliveTime 为 0 ,即线程启动后就不退出,缓冲队列为 LinkedBlockingQueue, 大小为整型的最大数
2. newSingleThreadExecutor()
创建大小为 1 的固定线程池
3. newCachedThreadPool()
创建 corePoolSize 为 0 最大线程数为整型的最大数,线程 keepAliveTime 为 1 分钟,缓存任务的队列为 SynchronousQueue 的线程池
4. newSheduledThreadPool(ing)
创建 corePoolSize 为传入参数,最大线程数为整型的最大数,线程 keepAliveTime 为 0, 缓存任务的队列为 DelayedWorkQueue 的线程池。在实际业务中,通常会有一些需要定时或延迟执行的任务,更典型的是在异步操作时需要超时回调的场景。
JDK5 之前,用 Timer 实现这个功能,两者区别:
1. Timer 单线程,一旦一个 task 运行缓慢,其它的 task 也会推迟, ScheduledThreadPool 并发
2. Timer 中的 task 抛出 RunntimeException 时,其它 task 也不会执行
3. ScheduledThreadPool 可以执行 callable 的 task
4.2.8 FutureTask
可用于异步获取执行结果或取消执行任务的场景,通过传入 Runnable 或 Callable 的任务给 FutureTask ,直接调用其 run 方法或放入线程池执行,之后可在外部通过 FutureTask 的 get 异步获取执行结果。 FutureTask 可以确保即使调用了多闪 run 方法,它都会只执行一次 Runnable 或 Callable 任务。
4.2.9 Semaphore
用于控制某资源同时被访问的个数的类,例如连接池中控制创建的连接个数。
4.2.10 CountDownLatch
可用于控制多个线程同时开始某动作的类,采用的方式为减计数方式。当计数减至零时,位于 latch.await 后的代码才会被执行。
4.2.11 CyclicBarrier
和 CountDownLatch 不同, CyclicBarrier 是当 await 数量达到设定的数量后,才继续向下执行。
4.2.12 ReentrantLock
一个更为方便的控制并发资源的类,和 synchronized 语法达到的效果是一致的。这两者之间如何取舍呢,借用 Brian Goetz 的话 (http://www.ibm.com/developerworks/cn/java/j-jtp10264/index.html) :
答案非常简单 —— 在确实需要一些 synchronized 所没有的特性的时候,比如时间锁等候、可中断锁等候、无块结构锁、多个条件变量或者锁投票。 ReentrantLock
还具有可伸缩性的好处,应当在高度争用的情况下使用它,但是请记住,大多数 synchronized 块几乎从来没有出现过争用,所以可以把高度争用放在一边。我建议用 synchronized 开发,直到确实证明 synchronized 不合适,而不要仅仅是假设如果使用 ReentrantLock
“ 性能会更好 ” 。请记住,这些是供高级用户使用的高级工具。(而且,真正的高级用户喜欢选择能够找到的最简单工具,直到他们认为简单的工具不适用为止。)。一如既往,首先要把事情做好,然后再考虑是不是有必要做得更快。
4.2.13 Condition
接口,典型实现 ReentrantLock , ReentrantLock 提供了一个 newCondition 方法,以便用户在同一个锁的情况下可以根据不同的情况执行等待或唤醒动作。
ReentrantLock.newCondition().await()
ReentrantLock..newCondition().signal()
4.2.14 RenentrantReadWriteLock
和 ReentrantLock 没有继承关系,它提供了读锁( ReadLock )和写锁( WriteLock ),比 ReentrantLock 的一把锁,读写锁分离对读多写少的情况,提高性能。
当调用读锁的 lock 方法时,没有线程持有写锁,就可获得读锁,意味着只要进行读操作,就没有其它线程进行写操作,读操作时无阻塞的;
当调用写锁的 lock 时,如果此时没有线程持有读锁或写锁,则可继续,意味着要进行写时,如果有其它线程进行读或写,就会被阻塞,
读写锁使用时的升级和降级机制:
同一线程中,持有读锁后,不能直接调用写锁的 lock
同一线程中,持有写锁后,可调用读锁的 lock 方法,之后如果调用写锁的 unlock 方法,那么当前锁将降级为读锁。
记作者的测试: 同时启动 102 个线程, 100 个进行读操作 (HashMap.get) , 2 个进行写操作 (HashMap.put), 较之 ReentrantLock , ReentrantReadWriteLock 性能提升了三倍多。
以上分析了 JDK5 以后版本提供的并发包中一些常用类的实现方法,这些类能够帮助开发者更好地控制高并发下的资源操作,尽可能地避免出现不一致及资源竞争激烈等现象。总的来说,基于 CAS 、拆分锁、 voliate 及 AbstractueuedSynchronier 是这些类的主要实现方法,这些方法都是为了尽量减少高并发时的竞争现象,对于实际编码有一定的参考价值,从而保障程序即使在 高并发时也能保持较高的性能。