`

读书笔记:《分布式JAVA应用 基础与实践》 第四章 分布式JAVA应用与JDK类库

阅读更多

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 是这些类的主要实现方法,这些方法都是为了尽量减少高并发时的竞争现象,对于实际编码有一定的参考价值,从而保障程序即使在 高并发时也能保持较高的性能。

 

 

 

 

 

 

 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics