文章目录
- 基础构建模块
- 同步容器类
- 同步容器类的问题
- 迭代器与ConcurrentModificationException
- 隐藏迭代器
- 并发容器
- ConcurrentHashMap
- 额外的原子Map操作
- CopyOnWriteArrayList
- 阻塞队列和生产者 - 消费者模式
- 示例:桌面搜索
- 串行线程封闭
- 双端队列与工作密取
- 阻塞方法与中断方法
- 同步工具类
- 闭锁
- FutureTask
- 信号量
- 栅栏
- 构建高效且可伸缩的结果缓存
基础构建模块
同步容器类
- 同步容器类包括 Vector 和 Hashtable,二者是早期 JDK 的一部分,此外还包括在JDK 1.2中添加的一些功能相似的类,这些同步的封装器类是由 Collections.synchronizedXxx 等工厂方法创建的。这些类实现线程安全的方式是:将他们的状态封装起来,并对每个公有方法都进行同步,使得每次只有一个线程能访问容器的状态。
同步容器类的问题
// Vector 上可能导致混乱结果的符合操作
public static Object getLast (Vector list) {
int lastIndex = list.size() - 1;
return list.get(lastIndex);
}
public static void deleteLast (Vector list) {
int lastIndex = list.size() - 1;
list.remove(lastIndex);
}
// 在使用客户端加锁的 Vector 上的符合操作
public static Object getLast (Vector list) {
synchronized (list) {
int lastIndex = list.size() - 1;
return list.get(lastIndex);
}
}
public static void deleteLast (Vector list) {
synchronized (list) {
int lastIndex = list.size() - 1;
list.remove(lastIndex);
}
}
- 在调用 size 和相应的 get 之间,Vector 的长度可能会发生变化,这种风险在对 Vector 中的元素进行迭代时仍然会出现。
// 可能抛出ArrayIndexOutOfBoundsException的迭代操作
for (int i = 0; i < vector.size(); i++) {
doSomething(vector.get(i));
}
// 带有客户端加锁的迭代
synchronized (vector) {
for (int i = 0; i < vector.size(); i++) {
doSomething(vector.get(i));
}
}
迭代器与ConcurrentModificationException
// 通过 Iterator 来迭代 List
List widgetList
= Collections.synchronizedList(new ArrayList());
...
// 可能抛出 ConcurrentModificationException
for (widget w : widgetList) {
doSomething(w);
}
隐藏迭代器
// 隐藏在字符串连接中的迭代操作
public class HiddenIterator {
@GuardedBy("this")
private final Set set = new HashSet();
public synchronized void add (Integer i) { set.add(i); }
public synchronized void remove (Integer i) { set.remove(i); }
public void addTenThings() {
Random r = new Random();
for (int i = 0; i < 10; i++) {
add(r.nextInt());
}
System.out.println("DEBUG:added ten elements to " + set);
}
}
- 正如封装对象的状态有助于维持不变性条件一样,封装对象的同步机制同样有助于确保实施同步策略。
并发容器
- Java 5.0 提供了多种并发容器类来改进同步容器的性能。同步容器将所有对容器状态的访问都串行化,以实现他们的线程安全性。这种方法的代价是严重降低并发性,当多个线程竞争容器的锁时,吞吐量将严重减低。
- 通过并发容器来代替同步容器,可以极大地提高伸缩性并降低风险。
ConcurrentHashMap
额外的原子Map操作
// ConcurrentMap接口
public interface ConcurrentMap extends Map {
// 仅当K没有相应的映射值才插入
V putIfAbsent (K key, V value);
// 仅当K被映射到V时才移除
boolean remove (K key, V value);
// 仅当K被映射到oldValue时才替换为newValue
boolean replace (K key, V oldValue, V newValue);
// 仅当K被映射到某个值时才替换为 newValue
V replace (K key, V newValue);
}
CopyOnWriteArrayList
阻塞队列和生产者 - 消费者模式
- 在构建高可靠的应用程序时,有界队列是一种强大的资源管理工具:他们能抑制并防止产生过多的工作项,使应用程序在负荷过载的情况下变得更加健壮。
示例:桌面搜索
public class FileCrawler implements Runnable {
private final BlockingQueue fileQueue;
private final FileFilter fileFilter;
private final root;
public void run () {
try {
crawl(root);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void crawl (File root) throws InterruptedException {
File[] entries = root.listFiles(fileFilter);
if (entries != null) {
for (File entry : entries) {
if (entry.isDirectory()) {
crawl(entry);
} else if (!alreadyIndexed(entry)) {
fileQueue.put(entry);
}
}
}
}
}
public class Indexer implements Runnable {
private final BlockingQueue queue;
public Indexer (BlockingQueue queue) {
this.queue = queue;
}
public void run () {
try {
while (true) {
indexFile(queue.take());
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// 启动桌面搜索
public static void startIndexing (File[] roots) {
BlockingQueue queue = new LinkedBlockingQueue(Bound);
FileFilter filter = new FileFilter() {
public boolean accept (File file) { return true; }
}
for (File root : roots) {
new Thread(new FileCrawler(queue, filter, root)).start();
}
for (int i = 0; i < N_CONSUMERS; i++) {
new Thread(new Indexer(queue)).start();
}
}
串行线程封闭
双端队列与工作密取
阻塞方法与中断方法
// 恢复中断状态以避免屏蔽中断
public class TaskRunnable implements Runnable {
BlockingQueue queue;
...
public void run () {
try {
processTask(queue.take());
} catch (InterruptedException e) {
// 恢复被中断的状态
Thread.currentThread().interrupt();
}
}
}
同步工具类
- 在容器类中,阻塞队列是一种独特的类:他们不仅能作为保存对象的容器,还能协调生产者和消费者等线程之间的控制流,因为take和put等方法将阻塞,直到队列达到期望的状态(队列既非空,也非满)。
同步工具类可以是任何一个对象,只要它根据其自身的状态来协调线程的控制流。阻塞队列可以作为同步工具类,其他类型的同步工具类还包括信号量(Semaphore)、栅栏(Barrier)以及闭锁(Latch)。
闭锁
- 闭锁是一种同步工具类,可以延迟线程的进度直到其到达终止状态。
// 在计时测试中使用 CountDownLatch 来启动和停止线程
public class TestHarness {
public long timeTasks (int nThreads, final Runnable task)
throws InterruptedException {
final CountDownLatch startGate = new CountDownLatch(1);
final CountDownLatch endGate = new CountDownLatch(nThreads);
for (int i = 0; i < nThreads; i++) {
Thread t = new Thread() {
public void run() {
try {
startGate.await();
try {
task.run();
} finally {
endGate.countDown();
}
} catch (InterruptedException ignored) { }
}
};
t.start();
}
long start = System.nanoTime();
startGate.countDown();
endGate.await();
long end = System.nanoTime();
return end - start;
}
}
FutureTask
// 使用 FutureTask 来提前加载稍后需要的数据
public class Preloader {
private final FutureTask future =
new FutureTask(new Callable() {
public ProductInfo call() throws DataLoadException {
return loadProductInfo();
}
});
private final Thread thread = new Thread(future);
public void start () { thread.start(); }
public ProductInfo get ()
throws DataLoadException, InterruptedException {
try {
return future.get();
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof DataLoadException) {
throw (DataLoadException) cause;
} else {
throw launderThrowable(cause);
}
}
}
}
信号量
// 使用 Semaphore 为容器设置边界
public class BoundedHashSet {
private final Set set;
private final Semaphore sem;
public BoundedHashSet (int bound) {
this.set = Collections.synchronizedSet(new HashSet());
sem = new Semaphore(bound);
}
public boolean add (T o) throws InterruptedException {
sem.acquire();
boolean wasAdded = false;
try {
wasAdded = set.add(o);
return wasAdded;
}
finally {
if (!wasAdded) {
sem.release();
}
}
}
public boolean remove (Object o) {
boolean wasRemoved = set.remove(o);
if (wasRemoved) {
sem.release();
}
return wasRemoved;
}
}
栅栏
// 通过 CyclicBarrier 协调细胞自动衍生系统中的计算
public class CellularAutomata {
private final Board mainBoard;
private final CyclicBarrier barrier;
private final Worker[] workers;
public CellularAutomata (Board board) {
this.mainBoard = board;
int count = Runtiome.getRuntime().availableProcessors();
this.barrier = new CyclicBarrier(count,
new Runnable() {
public void run () {
mainBoard.commitNewValues();
}
});
this.workers = new Worker[count];
for (int i = 0; i < count; i++) {
workers[i] = new Worker(mainBoard.getSubBoard(count, i));
}
}
private class Worker implements Runnable {
private final Board board;
public Worker (Board board) { this.board = board; }
public void run () {
while (!board.hasConverged()) {
for (int x = 0; x < board.getMaxX(); x++) {
for (int y = 0; y < board.getMaxY(); y++) {
board.setNewValue(x, y, computeValue(x, y));
}
try {
barrier.await();
} catch (InterruptedException ex) {
return;
} catch (BrokenBarrierException ex) {
return;
}
}
}
}
}
public void start () {
for (int i = 0; i < workers.length; i++) {
new Thread(workers[i]).start();
}
mainBoard.waitForConvergence();
}
}
构建高效且可伸缩的结果缓存
// 使用HashMap 和 同步机制来初始化缓存
public interface Computable {
V compute (A arg) throws InterruptedException;
}
public class ExpensiveFunction
implements Computable {
public BigInteger compute (String arg) {
// 在经过长时间的计算后
return new BigInteger(arg);
}
}
public class Memoizerl implements Computable {
@GuardedBy("this")
private final Map cache = new HashMap();
private final Computable c;
public Memoizerl (Computable c) {
this.c = c;
}
public synchronized V compute (A arg) throws InterruptedException {
V result = cache.get(arg);
if (result == null) {
result = c.compute(arg);
cache.put(arg, result);
}
return result;
}
}
// 用 ConcurrentHashMap 替换 HashMap
public class Memoizer2 implements Computable {
private final Map cache = new ConcurrentHashMap();
private final Computable c;
public Memoizer2 (Computable c) { this.c = c; }
public V compute (A arg) throws InterruptedException {
V result = cache.get(arg);
if (result == null) {
result = c.compute(arg);
cache.put(arg, result);
}
return result;
}
}
// 基于 FutureTask 的 Memoizing 封装器
public class Memoizer3 implements Computable {
private final Map> cache
= new ConcurrentHashMap>();
private final Computable c;
public Memoizer3 (Computable c) { this.c = c; }
public V compute (final A arg) throws InterruptedException {
Future f = cache.get(arg);
if (f == null) {
Callable eval = new Callable {
public V call () throws InterruptedException {
return c.compute(arg);
}
};
FutureTask ft = new FutureTask(eval);
f = ft;
cache.put(arg, ft);
ft.run(); // 在这里将调用 c.compute
}
try {
return f.get();
} catch (ExecutionException e) {
throw launderThrwable(e.getCause());
}
}
}
// Memoizer 的最终实现
public class Memoizer implements Computable {
private final ConcurrentMap> cache
= new ConcurrentHashMap> ();
private final Computable c;
public Memoizer (Computable c) { this.c = c; }
public V compute (final A arg) throws InterruptedException {
while (true) {
Future f = cache.get(arg);
if (f == null) {
Callable eval = new Callable() {
public V call() throws InterruptedException {
return c.compute(arg);
}
};
FutureTask ft = new FutureTask(eval);
f = cache.putIfAbsent(arg, ft);
if (f == null) { f = ft; ft.run(); }
}
try {
return f.get();
} catch (CancellationException e) {
cache.remove(arg, f);
} catch (ExecutionException e) {
throw launderThrowable(e.getCause());
}
}
}
}
- 可变状态是至关重要的。
- 所有的并发问题都可以归结为如何协调对并发状态的访问。可变状态越少,就越容易确保线程安全性。
- 尽量将域声明为 final 类型,除非需要他们是可变的。
- 不可变对象一定是线程安全的。
- 不可变对象能极大地降低并发编程的复杂性。他们更为简单而且安全,可以任意共享而无须使用加锁或保护性复制等机制。
- 封装有助于管理复杂性
- 在编写线程安全的程序时,虽然可以将所有数据都保存在全局变量中,但为什么要这样做?将数据封装在对象中,更易于维持不变性条件:将同步机制封装在对象中,更易于遵循同步策略。
- 用锁来保护每个可变变量。
- 当保护同一个不变性条件中的所欲变量时,要使用同一个锁。
- 在执行复合操作期间,要持有锁。
- 如果从多个线程中访问同一个可变变量时没有同步机制,那么程序会出现问题。
- 不要故作聪明地推断出不需要使用同步。
- 在设计过程中考虑线程安全,或者在文档中明确地指出它不是线程安全的。
- 将同步策略文档化。