下面的内容基本上来自于《Java并发编程实践》, 留个记录~
一,CountDownLatch
CountDownLatch是一个灵活的闭锁的实现,允许一个或多个线程等待一个事件集的发生。
闭锁的状态包括一个计数器,初始化为一个正数,用来表现需要等待的事件数。countDown方法对计数器做减操作,表示一个事件已经发生了,而await方法会一直阻塞直到计数器为0,或者等待线程中断以及超时。
下面是n个线程并发执行的例子
public class CountDownLatchTest {
public long concurrentTasks(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();
task.run();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
endGate.countDown();
}
}
};
t.start();
}
long start= System.nanoTime();
startGate.countDown();
endGate.await();
long end = System.nanoTime();
return end - start;
}
}
上面的例子使用了两个阀门,一个“开始阀门”, 一个“结束阀门”。开始阀门将计数器初始化为1,结束阀门将计数器初始化为线程的数量。每一个工作线程做的第一件事是等待开始阀门的打开,这样能确保所有的线程都准备好再开始工作。每个线程的最后一件事是将结束阀门减1,这样是控制线程有效的等待,直到最后一个线程完成任务,这样就能计算线程整个的用时了。
二, FutureTask
FutureTask的计算通过Callable实现的,它等价于一个可以携带结果的Runnable,并且有3个状态: 等待,运行和完成。完成包括所有计算以任意的方式结束,包括正常、取消和异常。一旦FutureTask进入完成状态,将会永远停止在这个状态上。
Future.get的行为依赖于任务的状态。如果它已经完成,get可以立即得到结果,否则会阻塞直到任务转入完成状态,然后返回结果或抛出异常。FutureTask把计算的结果从运行计算的线程传送到需要这个结果的线程: FutureTask的规约保证了这种传递建立在结果的安全发布基础之上。
public class FutureTaskTest {
private FutureTask<String> task = new FutureTask<String>(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("Thread name1: " + Thread.currentThread().getName());
Thread.sleep(3000);
return "AAA";
}
});
public String invokeTask() throws InterruptedException, ExecutionException {
String pattern = "HH:mm:ss";
DateFormat format = new SimpleDateFormat(pattern);
System.out.println("begin: " + format.format(new Date()));
new Thread(task).start();
System.out.println("Thread name2: " + Thread.currentThread().getName());
String result = task.get();
System.out.println("end: " + format.format(new Date()));
System.out.println("result: " + result);
return result;
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
FutureTaskTest test = new FutureTaskTest();
test.invokeTask();
}
}
运算的结果如下:
begin: 11:17:46
Thread name2: main
Thread name1: Thread-0
end: 11:17:49
result: AAA
由此可知,在执行get方法时,main线程等待FutureTask的计算结果,处于阻塞状态。
三, 信号量
信号量用来控制能够同时访问某特定资源的活动的数量,或者同时执行某一给定操作的数量。计数信号量可以用来实现资源池或者给一个容器限定边界。
信号量可以用来实现资源池,比如数据库连接池。有一个定长的池,当它为空时,你向它请求资源会失败。构建这种池很容易,然而当池为空时,你真正需要做的是阻塞它,然后在不为空时,再次解除阻塞。如果你以池的大小初始化一个Semaphore,在你从池中获取资源之前,你应该用acquire方法获取一个许可,调用release把许可放回资源池。acquire会一直阻塞,直到池不为空。
public class SemaphoreTest {
private Semaphore semaphore;
private List<String> stringList = new ArrayList<String>();
public SemaphoreTest(int n) {
semaphore = new Semaphore(n);
}
public void addElement(String ele) {
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
stringList.add(ele);
}
public String removeElement() {
String ele = stringList.remove(stringList.size() - 1);
semaphore.release();
return ele;
}
}