本篇文章个人在北京喝咖啡的时候突然想到的...这段时间就有想写几篇关于线程任务的博客,所以回家到之后就奋笔疾书的写出来发表了
接上一篇
java并发包小结(一):http://blog.csdn.net/aalansehaiyang52/article/details/8877579
Future 接口
Future 接口允许表示已经完成的任务、正在执行过程中的任务或者还没有开始执行的任务。通过 Future 接口,可以尝试取消还没有完成的任务,查询任务已经完成还是取消了,以及提取(或等待)任务的结果值。
FutureTask 类实现了 Future,并包括一些构造函数,允许将 Runnable 或 Callable(会发生结果的 Runnable)和 Future 接口封装。因为 FutureTask 也实现 Runnable,所以可以只将 FutureTask 提供给 Executor。一些提交方法(如 ExecutorService.submit())除了提交任务之外,还将返回 Future 接口。
Future.get() 方法获得任务盘算的结果(或如果任务完成,但有异常,则抛出 ExecutionException)。如果任务还没有完成,那么 Future.get() 将被阻塞,直到任务完成;如果任务已经完成,那么它将立即返回结果。
应用 Future 构建缓存
该示例代码与 java.util.concurrent 中的多个类关联,凸起表现了 Future 的功能。它实现缓存,应用 Future 描述缓存值,该值可能已经盘算,或者可能在其他线程中'正在构造'。
它利用 ConcurrentHashMap 中的原子 putIfAbsent() 方法,确保唯一一个线程试图盘算给定关键字的值。如果其他线程随后请求同一关键字的值,它仅能等待(通过 Future.get() 的帮助)第一个线程完成。因此两个线程不会盘算相同的值。
public class Cache { ConcurrentMap map = new ConcurrentHashMap(); Executor executor = Executors.newFixedThreadPool(8); public V get(final K key) { FutureTask f = map.get(key); if (f == null) { Callable c = new Callable() { public V call() { // return value associated with key } }; f = new FutureTask(c); FutureTask old = map.putIfAbsent(key, f); if (old == null) executor.execute(f); else f = old; } return f.get(); } }
Semaphore
Semaphore 类实现标准 Dijkstra 计数信号。计数信号可以认为拥有必定数量的许可权,该许可权可以获得或释放。如果有残余的许可权,acquire() 方法将成功,否则该方法将被阻塞,直到有可用的许可权(通过其他线程释放许可权)。线程一次可以获很多个许可权。
注意信号不跟踪哪个线程拥有多少许可权;这由应用程序来决定,以确保何时线程释放许可权,该信号表示其他线程拥有许可权或者正在释放许可权,以及其他线程知道它的许可权已释放。
CountdownLatch
CountdownLatch 类与 CyclicBarrier 相似,因为它的角色是对已经在它们中间分摊了问题的一组线程停止协调。它也是应用整型变量构造的,指明计数的初始值,但是与 CyclicBarrier 不同的是,CountdownLatch 不能从新应用。
其中,CyclicBarrier 是到达屏障的全部线程的大门,只有当全部线程都已经到达屏障或屏障被打破时,才允许这些线程通过,CountdownLatch 将到达和等待功能分离。任何线程都可以通过调用 countDown() 增加以后计数,这类不会阻塞线程,而只是增加计数。await() 方法的行为与 CyclicBarrier.await() 稍微有所不同,调用 await() 任何线程都会被阻塞,直到闩锁计数增加为零,在该点等待的全部线程才被释放,对 await() 的后续调用将立即返回。
当问题已经分解为许多部分,每个线程都被分配一部分盘算时,CountdownLatch非常有用。在任务线程结束时,它们将增加计数,协调线程可以在闩锁处等待以后这一批盘算结束,然后继承移至下一批盘算。
相反地,拥有计数 1 的 CountdownLatch 类可以用作'启动大门',来立即启动一组线程;任务线程可以在闩锁处等待,协调线程增加计数,从而立即释放全部任务线程。下例应用两个 CountdownLatche。一个作为启动大门,一个在全部任务线程结束时释放线程:
class Driver { // ... void main() throws InterruptedException { CountDownLatch startSignal = new CountDownLatch(1); CountDownLatch doneSignal = new CountDownLatch(N); for (int i = 0; i < N; ++i) // create and start threads new Thread(new Worker(startSignal, doneSignal)).start(); doSomethingElse(); // don't let them run yet startSignal.countDown(); // let all threads proceed doSomethingElse(); doneSignal.await(); // wait for all to finish } } class Worker implements Runnable { private final CountDownLatch startSignal; private final CountDownLatch doneSignal; Worker(CountDownLatch startSignal, CountDownLatch doneSignal) { this.startSignal = startSignal; this.doneSignal = doneSignal; } public void run() { try { startSignal.await(); doWork(); doneSignal.countDown(); } catch (InterruptedException ex) {} // return; } }
Hashtable 与 ConcurrentHashMap
作为可伸缩性的例子,ConcurrentHashMap 实现计划的可伸缩性要比其线程安全的上一代 Hashtable 的可伸缩性强很多。Hashtable 一次只允许一个线程拜访 Map;ConcurrentHashMap 允许多个读者并发执行,读者与写入者并发执行,以及一些写入者并发执行。因此,如果许多线程频繁拜访共享映射,应用 ConcurrentHashMap 的总的吞吐量要比应用 Hashtable 的好。
下表大致说明白 Hashtable 和 ConcurrentHashMap 之间的可伸缩性差别。在每次运行时,N 个线程并发执行严密循环,它们从 Hashtable 或 ConcurrentHashMap 中检索随即关键字,60% 的失败检索将执行 put() 操纵,2% 的成功检索执行 remove() 操纵。测试在运行 Linux 的双处理器 Xeon 系统中执行。数据表现 10,000,000 个迭代的运行时间,对于 ConcurrentHashMap,标准化为一个线程的情况。可以看到直到许多线程,ConcurrentHashMap 的性能仍保持可伸缩性,而 Hashtable 的性能在涌现锁定竞争时几乎立即下降。
与平日的服务器应用程序相比,这个测试中的线程数看起来很少。然而,因为每个线程未停止其他操纵,仅是重复地选择应用该表,所以这样可以模拟在执行一些实际任务的情况下应用该表的大批线程的竞争。
线程 | ConcurrentHashMap | Hashtable |
1 | 1.0 | 1.51 |
2 | 1.44 | 17.09 |
4 | 1.83 | 29.9 |
8 | 4.06 | 54.06 |
16 | 7.5 | 119.44 |
32 | 15.32 | 237.2 |
文章结束给大家分享下程序员的一些笑话语录: 小沈阳版程序员~~~ \n程序员其实可痛苦的了......需求一做一改,一个月就过去了;嚎~ \n需求再一改一调,一季度就过去了;嚎~ \n程序员最痛苦的事儿是啥,知道不?就是,程序没做完,需求又改了; \n程序员最最痛苦的事儿是啥,知道不? 就是,系统好不容易做完了,方案全改了; \n程序员最最最痛苦的事儿是啥,知道不? 就是,系统做完了,狗日的客户跑了; \n程序员最最最最最痛苦的事儿是啥,知道不? 就是,狗日的客户又回来了,程序给删没了!
--------------------------------- 原创文章 By
线程和任务
---------------------------------