闭锁

  一种可以延迟线程的进度直到其到达终止状态.可以用来确保某些活动直到其他活动都完成后才继续执行

  例如:

  1. 确保某个计算在其需要的所有资源都被初始化了之后才继续执行.
  2. 确保某个服务在其他依赖的服务都启动了之后才开始执行
  3. 等待某个操作的所有参与者(如LOL) 都就绪了之后再继续执行.

锁的实现

1.CountDownLatch

  CountDownLatch 是一种灵活的闭锁实现. 可以在以上的各种类型情况下使用.它可以使一个或多个线程等待一组事件的发生.

  闭锁状态包括一个计数器,该计数器被初始化为一个正数.表示需要被等待的事件的数量. countDown 方法用于递减计数器,表示有一个事件已经发生,而await 方法等待计数器到达零时就会执行, 否则会一直阻塞直到计数器为零,或者等待中的线程中断, 或者等待超时.

import java.util.concurrent.*;

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;
} public static void main(String[] args) throws InterruptedException {
new TestHarness().timeTasks(9, new Runnable() {
public void run() {
System.out.println(this);
}
});
}
}

  以上程序.它使用两个闭锁,分别表示起始门 "startGate" 和 结束门 "endGate" 来确保所有线程都准备就绪后才继续执行,而每个线程做的最后一件事都是让 "endGate" 减一,这能使主线程高效地等待直到所有工作线程都执行完成,因此可以统计所消耗的时间.

2.FutureTask

  futureTask 也可以用作闭锁. futureTask 是通过Callable 来实现的. 相当于一种可用于生产结果的runnable , 并且可以用于以下3钟等待状态

  1. 等待运行(Waiting to run )
  2. 正在运行(Running)
  3. 运行完成(completed)

执行完成 ,表示计算的所有可能结束的方式. 包括 正常结束,由于取消而结束和由于异常而结束等.

Future.get() 的行为取决于任务的状态,如果任务已完成,那么get 会立即返回结果,get 将阻塞直到这个任务进去完成状态.然后返回结果或者抛出异常. FutureTask 将计算结果从执行计算的线程传递到获取这个结果的线程, 而 FutureTask 的规范确保了这种传递的过程能实现结果的安全发布.

public class Preloader {
ProductInfo loadProductInfo() throws DataLoadException {
return null;
} private final FutureTask<ProductInfo> future = new FutureTask<ProductInfo>(
new Callable<ProductInfo>() {
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.launderThrowable(cause);
}
} interface ProductInfo {
}
} class DataLoadException extends Exception {
}

3. Semaphore

  计数信号量(Counting Semaphore) 用来控制同是访问某个特定资源的操作数量,或者同时执行某个指定操作的数量. 计数信号量还可以用来实现某种资源池,或者对容器施加边界(如:blockingQueue)

  Semaphore 中管理着一组虚拟许可(permit), 许可的初始化数量可以通过构造函数来指定,在执行操作前先获取许可(只要还有剩余许可),并在使用后释放,如果没有许可,那么acquire 将阻塞到有许可(或者直到被中断或者操作超时). release 方法将返回一个许可给信号量(许可与线程无关,一个许可可以在一个线程获取,在另一个线程释放,且不具备重入性)

public class BoundedHashSet <T> {
private final Set<T> set;
private final Semaphore sem; public BoundedHashSet(int bound) {
this.set = Collections.synchronizedSet(new HashSet<T>());
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;
}
}

4. Barrier

  栅栏 类似于闭锁,它能阻塞一组线程直到某个事件发生,栅栏与闭锁的区别关键在于:所有线程必须同时到达栅栏的位置,才能继续,闭锁用于等待某件事情,而栅栏用于实现一些协议.例如: 几个人决定在某个地方集合:'所有人6:00 在 麦当劳碰头,到了以后要等其他人,之后再讨论下一步要做的事.'

  CyclicBarrier 可以使一定数量的参与方法反复在栅栏位置会聚,它在并行迭代算法中非常有用,这种算法通常将一个问题拆分成一系列相互独立的子问题,当线程到达栅栏位置时将调用await 方法, 这个方法将阻塞直到所有线程都到达栅栏位置,如果所有线程都到达栅栏位置,那么栅栏将打开, 此时所有线程都被释放,而栅栏将被重置以便下次使用, 如果对await的调用超时,或者await 阻塞的线程被中断, 那么栅栏将被认为是打破了, 所有阻塞的await 调用都将终止并抛出 BrokenBarrierException . 如果成功通过栅栏,那么await 将为每个线程返回一个唯一的到达索引号,我们可以利用这些索引来"选举" 产生一个领导线程.并在下一次迭代中由该领导线程执行一些特殊的工作.CyclicBarrier 还可以是你将一个栅栏操作传递给构造函数,这是一个Runnable ,当成功通过栅栏时会(在一个子任务线程中) 执行它.但在阻塞线程被释放钱是不能执行的.

  在模拟程序中经常使用栅栏.

  

public class CellularAutomata {
private final Board mainBoard;
private final CyclicBarrier barrier;
private final Worker[] workers; public CellularAutomata(Board board) {
this.mainBoard = board;
int count = Runtime.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;
}
}
} private int computeValue(int x, int y) {
// Compute the new value that goes in (x,y)
return 0;
}
} public void start() {
for (int i = 0; i < workers.length; i++)
new Thread(workers[i]).start();
mainBoard.waitForConvergence();
} interface Board {
int getMaxX();
int getMaxY();
int getValue(int x, int y);
int setNewValue(int x, int y, int value);
void commitNewValues();
boolean hasConverged();
void waitForConvergence();
Board getSubBoard(int numPartitions, int index);
}
}

张孝祥的案例:

public class CyclicBarrierTest {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
final CyclicBarrier cb = new CyclicBarrier(3); // 三个线程同时到达
for (int i = 0; i < 3; i++) {
Runnable runnable = new Runnable() {
public void run() {
try {
Thread.sleep((long) (Math.random() * 10000));
System.out.println("线程"
+ Thread.currentThread().getName()
+ "即将到达集合地点1,当前已有"
+ (cb.getNumberWaiting() + 1)
+ "个已到达"
+ (cb.getNumberWaiting() == 2 ? "都到齐了,继续走啊"
: "正在等候"));
try {
cb.await();
} catch (BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Thread.sleep((long) (Math.random() * 10000));
System.out.println("线程"
+ Thread.currentThread().getName()
+ "即将到达集合地点2,当前已有"
+ (cb.getNumberWaiting() + 1)
+ "个已到达"
+ (cb.getNumberWaiting() == 2 ? "都到齐了,继续走啊"
: "正在等候"));
try {
cb.await();
} catch (BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Thread.sleep((long) (Math.random() * 10000));
System.out.println("线程"
+ Thread.currentThread().getName()
+ "即将到达集合地点3,当前已有"
+ (cb.getNumberWaiting() + 1)
+ "个已到达"
+ (cb.getNumberWaiting() == 2 ? "都到齐了,继续走啊"
: "正在等候"));
try {
cb.await();
} catch (BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
service.execute(runnable);
}
service.shutdown();
}
}

  另一种形式的栅栏是 Exchanger ,它是一种两方(Two-Party)栅栏 , 各方在栅栏位置上交换数据,当两方执行不对称的操作时, Exchanger 会非常有用.

  例如: 当一个线程想缓冲区写入数据, 而另一个线程用缓冲区中读取数据.这些线程可以使用Exchanger来汇聚,并将满的缓冲区与空的缓冲区交换.当两个线程通过Exchanger交换对象时.这种交换就把两个对象安全地发布给另一方.

  数据交换的实际取决于应用程序的相应需求. 最简单的方案是. 当缓冲区被填满时,由填充任务进行交换. 当缓冲区为空时,由清空任务进行交换. 这样会把需要交换的次数降至最低, 但如果新数据的到达不可预测,那么一些数据的处理过程就将延迟.另一个方法是,不仅当缓冲区被填满时进行交换. 并且当缓冲区被充到一定程度,并保持一段时间后.也进行交换.

/**
*
*/
package mjorcen.nio.test2; import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Exchanger; /**
*
*
* @author mjorcen
* @email mjorcen@gmail.com
* @dateTime Jan 19, 2015 6:57:56 PM
* @version 1
*/
public class ExchangerTest {
final Exchanger<List<String>> exchanger; public ExchangerTest(Exchanger<List<String>> exchanger) {
super();
this.exchanger = exchanger;
} public static void main(String[] args) {
Exchanger<List<String>> exchanger = new Exchanger<List<String>>(); new Thread(new ExchangerThread01(exchanger)).start();
new Thread(new ExchangerThread02(exchanger)).start(); }
} class ExchangerThread01 implements Runnable {
final Exchanger<List<String>> exchanger; public ExchangerThread01(Exchanger<List<String>> exchanger) {
super();
this.exchanger = exchanger;
} /*
* (non-Javadoc)
*
* @see java.lang.Runnable#run()
*/
public void run() {
System.out.println("ExchangerThread01 begin ... ");
try {
List<String> list = new LinkedList<String>();
for (int i = 0; i < 20; i++) {
list.add("str_01_" + i);
}
list = exchanger.exchange(list);
for (String string : list) {
System.out.println("Thread01 is " + string);
}
System.out.println("ExchangerThread01 end ... ");
} catch (InterruptedException e) {
e.printStackTrace();
}
} } class ExchangerThread02 implements Runnable {
final Exchanger<List<String>> exchanger; public ExchangerThread02(Exchanger<List<String>> exchanger) {
super();
this.exchanger = exchanger;
} /*
* (non-Javadoc)
*
* @see java.lang.Runnable#run()
*/
public void run() {
System.out.println("ExchangerThread02 begin... ");
List<String> list = new LinkedList<String>();
for (int i = 0; i < 10; i++) {
list.add("str_02_" + i);
}
try {
Thread.sleep(1000);
list = exchanger.exchange(list);
for (String string : list) {
System.out.println("Thread02 is " + string);
}
System.out.println("ExchangerThread02 end ... ");
} catch (InterruptedException e) {
e.printStackTrace();
}
} }

6. 构建高效且可伸缩的结果缓存

public class Memoizer <A, V> implements Computable<A, V> {
private final ConcurrentMap<A, Future<V>> cache
= new ConcurrentHashMap<A, Future<V>>();
private final Computable<A, V> c; public Memoizer(Computable<A, V> c) {
this.c = c;
} public V compute(final A arg) throws InterruptedException {
while (true) {
Future<V> f = cache.get(arg);
if (f == null) {
Callable<V> eval = new Callable<V>() {
public V call() throws InterruptedException {
return c.compute(arg);
}
};
FutureTask<V> ft = new FutureTask<V>(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.launderThrowable(e.getCause());
}
}
}
}

以上内容出自: <<java并发编程实践>>

java 并发编程的更多相关文章

  1. 【Java并发编程实战】----- AQS(四):CLH同步队列

    在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形.其主要从两方面进行了改造:节点的结构与节点等待机制.在结构上引入了头 ...

  2. 【Java并发编程实战】----- AQS(三):阻塞、唤醒:LockSupport

    在上篇博客([Java并发编程实战]----- AQS(二):获取锁.释放锁)中提到,当一个线程加入到CLH队列中时,如果不是头节点是需要判断该节点是否需要挂起:在释放锁后,需要唤醒该线程的继任节点 ...

  3. 【Java并发编程实战】----- AQS(二):获取锁、释放锁

    上篇博客稍微介绍了一下AQS,下面我们来关注下AQS的所获取和锁释放. AQS锁获取 AQS包含如下几个方法: acquire(int arg):以独占模式获取对象,忽略中断. acquireInte ...

  4. 【Java并发编程实战】-----“J.U.C”:CLH队列锁

    在前面介绍的几篇博客中总是提到CLH队列,在AQS中CLH队列是维护一组线程的严格按照FIFO的队列.他能够确保无饥饿,严格的先来先服务的公平性.下图是CLH队列节点的示意图: 在CLH队列的节点QN ...

  5. 【Java并发编程实战】-----“J.U.C”:CountDownlatch

    上篇博文([Java并发编程实战]-----"J.U.C":CyclicBarrier)LZ介绍了CyclicBarrier.CyclicBarrier所描述的是"允许一 ...

  6. 【Java并发编程实战】-----“J.U.C”:CyclicBarrier

    在上篇博客([Java并发编程实战]-----"J.U.C":Semaphore)中,LZ介绍了Semaphore,下面LZ介绍CyclicBarrier.在JDK API中是这么 ...

  7. 【Java并发编程实战】-----“J.U.C”:ReentrantReadWriteLock

    ReentrantLock实现了标准的互斥操作,也就是说在某一时刻只有有一个线程持有锁.ReentrantLock采用这种独占的保守锁直接,在一定程度上减低了吞吐量.在这种情况下任何的"读/ ...

  8. Java并发编程:volatile关键字解析

    Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在 ...

  9. JAVA并发编程J.U.C学习总结

    前言 学习了一段时间J.U.C,打算做个小结,个人感觉总结还是非常重要,要不然总感觉知识点零零散散的. 有错误也欢迎指正,大家共同进步: 另外,转载请注明链接,写篇文章不容易啊,http://www. ...

  10. 学习笔记:java并发编程学习之初识Concurrent

    一.初识Concurrent 第一次看见concurrent的使用是在同事写的一个抽取系统代码里,当时这部分代码没有完成,有许多的问题,另一个同事接手了这部分代码的功能开发,由于他没有多线程开发的经验 ...

随机推荐

  1. ANDROID内存优化——大汇总(转)

    原文作者博客:转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! ANDROID内存优化(大汇总——上) 写在最前: 本文的思路主要借鉴了20 ...

  2. Java之奇偶组合

    写一个函数,将已知数组的奇数项组合成一个新的数组,在函数中调用该数组,并且输出新数组的内容. 定义一个数组,该数组为{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17 ...

  3. Java类加载的时机_4种主动引用会触犯类加载+剩下的被动引用不会触发类的加载

    转载自:http://chenzhou123520.iteye.com/blog/1597597 Java虚拟机规范没有强制性约束在什么时候开始类加载过程,但是对于初始化阶段,虚拟机规范则严格规定了有 ...

  4. ASP.NET 使用 System.Web.Script.Serialization 解析 JSON (转)

    JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.易于人阅读和编写.同时也易于机器解析和生成.它基于JavaScript Programming Langu ...

  5. C#读写日志文本文件

    日志为文本文件每列以制表符隔开 行以换行符隔开 本次示例简单实现如下相关功能:1.正写日志文本 最新的日志放后面2.倒写日志文本 最新的日志放前面3.读日志文本内容显示在Label4.读日志文本内容到 ...

  6. 【Unity3D实战】方块跑酷初级开发实战(一)

    [Unity3D实战]方块跑酷初级开发实战(一) 欢迎大家来到LDS的博客,今天开始我们讲解一下跑酷类游戏的基本操作,本文为原创,视频请观看[ http://www.mkcode.net/html/u ...

  7. 使用Struts2 验证框架,验证信息重复多次出现

    版权声明:本文为博主原创文章,未经博主允许不得转载. 问题描述:第一次提交表单.某个数据不符合规则,就会出现一条错误信息.再次提交,上次显示的错误信息不消失,又多出一条一模一样的错误信息.提交几次,就 ...

  8. 20150311—html中iframe(转发)

    JS实现iframe框架页面跳转和刷新 一.js方式的页面跳转 1.window.location.href方式 <script language="javascript" ...

  9. nyoj71--独木舟上的旅行

    描述 进行一次独木舟的旅行活动,独木舟可以在港口租到,并且之间没有区别.一条独木舟最多只能乘坐两个人,且乘客的总重量不能超过独木舟的最大承载量.我们要尽量减少这次活动中的花销,所以要找出可以安置所有旅 ...

  10. 一点总结-关于debug比赛

    上午的题目是: 1. main里面定义的变量必须手动初始化,使用memset或者其他,函数外或者函数内,会进行初始化为0. 2. 最长回文子串的马拉车manacher算法,不会写! 3. 数字三角形d ...