java.util.concurrent 包
原子量、并发集合、同步器、可重入锁
在大型应用程序中,把线程管理和创建工作与应用程序的其余部分分离开更有意义。
线程池封装线程管理和创建线程对象。
可以使用 volatile 变量来以比同步更低的成本存储共享变量,但它们有局限性。虽然
可以保证其他变量可以立即看到对 volatile 变量的写入,但无法呈现原子操作的读-修改-写
顺序,这意味着 volatile 变量无法用来可靠地实现互斥(互斥锁定)或计数器。
使用锁synchronized 当线程被阻塞来等待锁时,它无法进行其他
任何操作。如果阻塞的线程是高优先级的任务,那么该方案可能造成非常不好的结果
使用锁synchronized 还有一些其他危险,如死锁(当以不一致的顺序获得多个锁时会发生死锁)。
CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置”
基于 CAS 的并发算法称为“无锁定算法”,因为线程不必再等待锁定。“无锁定算法”要求某个线程总是执行操作。
java.util.concurrent.atomic 包
中提供了原子变量的 9 种风格(AtomicInteger、AtomicLong、 AtomicReference、AtomicBoolean
原子整型、长型、 及原子标记引用和戳记引用类的数组形式,其原子地更新一对值)
大多数用户都不太可能自己使用原子变量开发无阻塞算法, 更可能使用
java.util.concurrent 中提供的版本,如 ConcurrentLinkedQueue。
-------------------------------------------------------------------------
同步器
=================
1 Semaphore
类 java.util.concurrent.Semaphore 提供了一个计数信号量,从概念上讲,信号量维护了一
个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release()
添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore
只对可用许可的号码进行计数,并采取相应的行动。
Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。
2 CyclicBarrier
java.util.concurrent.CyclicBarrier 一个同步辅助类,它允许 (common barrier point)。
在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,
此时 CyclicBarrier 很有用。一组线程互相等待,直到到达某个公共屏障点。
因为该 barrier 在释放等待线程后可以重用,所以称它为循环的 barrier。
3 CountDownLatch
类 java.util.concurrent.CountDownLatch 是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,
它允许一个或多个线程一直等待。
用给定的数字作为计数器初始化 CountDownLatch。一个线程调用 await()方法后,在当前计数到达零之前,会一直受阻塞。
其他线程调用 countDown() 方法,会使计数器递减,所以,计数器的值为 0 后,会释放所有等待的线程。
其他后续的 await 调用都将立即返回。
这种现象只出现一次,因为计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。
CountDownLatch 作为一个通用同步工具,有很多用途。使用“ 1 ”初始化的
CountDownLatch 用作一个简单的开/关锁存器,或入口:在通过调用 countDown() 的线程
打开入口前,所有调用 await 的线程都一直在入口处等待。用 N 初始化的 CountDownLatch
可以使一个线程在 N 个线程完成某项操作之前一直等待,或者使其在某项操作完成 N 次之前一直等待。
4 Exchanger
类 java.util.concurrent.Exchanger 提供了一个同步点,在这个同步点,一对线程可以交换
数据。每个线程通过 exchange()方法的入口提供数据给他的伙伴线程,并接收他的伙伴线程
提供的数据,并返回。
线程间可以用 Exchanger 来交换数据。当两个线程通过 Exchanger 交互了对象,这个交换对于两个线程来说都是安全的。
5 Future 和 FutureTask
接口 public interface Future<V> 表示异步计算的结果。它提供了检查计算是否完成的方法,
以等待计算的完成,并调用get()获取计算的结果。
FutureTask 类是 Future 的一个实现, 并实现了Runnable ,所以可通过 Executor(线程池) 来执行。
也可传递给Thread对象执行。
如果在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给
Future 对象在后台完成,当主线程将来需要时,就可以通过 Future 对象获得后台作业的计算结果或者执行状态。
FutureTask ft= new FutureTask(new Callable<V>() {
@Override
public V call() throws Exception {
return null;
}
});
new Thread(ft);
=====================================
显示锁
========
1 ReentrantLock
什么时候才应该使用 ReentrantLock 呢?
答案非常简单 —— 在确实需要一些 synchronized 所没有的特性的时候,比如时间锁等候、可中断锁等候、无块结构锁、
多个条件变量或者锁投票。
ReentrantLock 还有两个比较重要的方法是:tryLock()和 tryLock(long timeout, TimeUnit unit) 。
tryLock()仅在调用时锁未被另一个线程保持的情况下,才获取该锁。
后者如果锁在给定等待时间内没有被另一个线程持有,且当前线程未被中断,则获取该锁。
2 ReentrantReadWriteLock
ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要
没有 writer,读取锁可以由多个 reader 线程同时保持。写入锁是独占的。
与互斥锁相比,读-写锁允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,
但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程),读-写锁利用了这一点。
从理论上讲,与互斥锁相比,使用读-写锁所允许的并发性增强将带来更大的性能提高。
在实践中,只有在多处理器上并且只在访问模式适用于共享数据时,才能完全实现并发性增强。
与互斥锁相比,使用读-写锁能否提升性能则取决于读写操作期间读取数据相对于修改
数据的频率,以及数据的争用——即在同一时间试图对该数据执行读取或写入操作的线程数。
=========================
Fork-Join 框架
--
1 应用 Fork-Join
在 JDK 7 中,java.util.concurrent 包的新增功能之一是一个 fork-join 风格的并行分解框
架。fork-join 概念提供了一种分解多个算法的自然机制,可以有效地应用硬件并行性。
并行分解方法常常称作 fork-join,因为执行一个任务将首先分解(fork)为多个子任务,然后再合并(join)(完成
后)。
fork-join 框架通过一种称作工作窃取(work stealing) 的技术减少了工作队列的争用情
况。每个工作线程都有自己的工作队列,这是使用双端队列(或者叫做 deque)来实现的(Java 6 在类库中添加了几种 deque 实现,包括 ArrayDeque 和 LinkedBlockingDeque)。当一个任务划分一个新线程时,它将自己推到 deque 的头部。当一个任务执行与另一个未完成任务的合并操作时,它会将另一个任务推到队列头部并执行,而不会休眠以等待另一任务完成
(像 Thread.join() 的操作一样)。当线程的任务队列为空,它将尝试从另一个线程的 deque 的尾部 窃取另一个任务。
fork-join 方法提供了一种表示可并行化算法的简单方式,而不用提前了解目标系统将提供多大程度的并行性。
所有的排序、搜索和数字算法都可以进行并行分解(以后,像 Arrays.sort() 这样的标准库机制将会使用 fork-join 框架,
允许应用程序免费享有并行分解的益处)。随着处理器数量的增长,我们将需要在程序内部使用更多的并行性,以有效利用
这些处理器;对计算密集型操作(比如排序)进行并行分解,使程序能够更容易利用未来的硬件。
2 应用 ParallelArray
在主流服务器应用程序中,最适合更细粒度并行性的地方是数据集的排序、搜索、选择和汇总。
其中的每个问题都可以用 divide-and-conquer(拆分-合并) 轻松地并行化,并能轻松地表示为
fork-join 任务。
例如,要将对大数据集求平均值的操作并行化,可以递归地将大数据集分解成更小的数据集
— 就像在合并排序中做的那样 — 对子集求均值子集的平均值的加权平均值。
对于排序和搜索问题,fork-join 库提供了一种表示可以并行化的数据集操作的非常简单的途径:ParallelArray 类。
其思路是:
用 ParallelArray 表示一组结构上类似的数据项,用 ParallelArray 上的方法创建一个对分解数据的具体方法的描述。
然后用该描述并行地执行数组操作(幕后使用的是 fork-join 框架)。这种方法支持声明性地指定数据选择、转换和处理操作,
允许框架计算出合理的并行执行计划,就像数据库系统允许用 SQL 指定数据操作并隐藏操作的实现机制一样。
ParallelArray 的一些实现可用于不同的数据类型和大小,包括对象数组和各种原语组成的数组。
随着可用的处理器数量增加,我们需要发现程序中更细粒度的并行性来源。最有吸引力
候选方案之一是聚合数据操作——排序、搜索和汇总。JDK 7 中将引入的 fork-join 库提供
了一种 “轻松表示” 某类可并行化算法的途径,从而让程序能够在一些硬件平台上有效运
行。通过声明性地描述想要执行的操作,然后让 ParallelArray 确定具体的执行方法,fork-join 库的
ParallelArray 组件使并行聚合操作的表示变得更加简单。