这是Ted Neward在IBM developerWorks中5 things系列文章中的一篇,仍然讲述了关于Java并发集合API的一些应用窍门,值得大家学习。(2010.06.17最后更新)
摘要:除了便于编写并发应用的集合API外,java.util.concurrent还引入了其它的预置程序组件,这些组件能辅助你在多线程应用中控制和执行线程。Ted Neward再介绍了五个来自于java.util.concurrent的Java编程必备窍门。
通过提供线程安全,性能良好的数据结构,并发集合框架使并发编程变得更容易。然而在有些情况下,开发者需要多走一步,并要考虑控制和/或调节线程的执行。提供java.util.concurrent包的全部原因就是为了简化多线程编程--事实上正是如此。
接着第一部分,本文介绍了多个同步数据结构,这些数据结构比核心语言基本结构(监视器)的层次要高,但不会高到将它们圈囿在一个集合类中。一旦知道了这些锁与栓的用途,就能径直去用了。
1. 信号量
在有些企业级系统中,常需要开发者去控制针对特定资源的请求(线程或动作)的数量。虽然完全可能试着手工编写这样的调节程序,但使用Semaphore类会更容易些,该类会为你处理对线程的控制,如清单1所示:
清单1. 使用信号量调节线程
虽然上例有10个线程在运行(针对运行SemApp的Java进程执行jstack程序可以验证这一点),但只有3个是活动的。另外7个线程会被保存起来,直到其中一个信号量计数器被释放出来。(准确地说,Semaphore类支持一次获取和释放一个以上的被许可线程,但在此处的场景中这么做没有意义。)
2. CountDownLatch
如果并发类Semaphore是被设计为在同一时刻允许"其中"一个线程执行的话,那么CountDownLatch就是赛马比赛中的起跑门。该类持有所有的线程,当遇到某个特定条件,那时CountDownLatch就会一次性释放全部的线程。
清单2. CountDownLatch:让我们比赛!
注意在清单2中,CountDownLatch服务于两个目的:首先,它同时释放所有的线程,模拟比赛的开始;但之后,另一个CountDownLatch模拟了比赛的结束。一场比赛会有更多的评论,你可以在比赛的"转弯"和"半程"点添加 CountDownLatch,当马匹跑过1/4程,半程和3/4程时。
3. Executor
清单1和清单2中的例子都遭遇了一个令人非常沮丧的错误,你被迫要直接地创建Thread对象。这是一个造成麻烦的方式,因为在有些JVM中,创建 Thread对象是一件重量级的工作,所以重用而非创建新的线程要好得多。然而在另一些JVM中,情况就恰恰相反:Thread是非常轻量级的,若你需要一个线程,直接创建它则会好得多。当然,如果Murphy有他自己的方法(他经常就是这么做的),无论你使用哪种方法,对于你最终所依赖的某种Java平台都会是错误的。
JSR-166专家组在一定程度上预见到了这种情况。与让Java开发者直接创建Thread实例不同,他们推荐Executor接口,这是一个创建新线程的抽象。如果清单3所示,Executor允许你自己不必使用new操作符去创建Thread对象:
清单3. Executor
使用Excutor的主要缺点与我们使用所有对象工厂所遇到的缺点一样:工厂必须来源于某处。不幸地是,不同于CLR,JVM并不带有一个标准的VM范围内的线程池。
Executor类只是作为获取Executor实现实例的常用地方,但它只有new方法(例如,为了创建新的线程池);它没有预创建的实例。所以,如果你想创建并使用一个能贯穿于整个程序的Executor实现,你就可以创建一个你自己的Executor实例。(或者,在有些情况下,你可以使用你所选容器/平台所提供的Executor实例。)
ExecutorService,为你服务
ExecutorService的用处在于使你不必关心Thread来自于何处,Executor接口缺乏Java开发者可能期望的一些功能,比如启动一个线程,该线程用于产生结果,它会以非阻塞方式一直等待,直到结果出现为止。(在桌面应用中这是很普通的需求,在这种应用中用户会执行一个需要访问数据库的UI操作,如果它耗时太长的话,就可能想要在它完成之前就取消这一操作。)
为此,JSR-166的专家们创造一个更为有用的抽象,ExecutorService接口,该接口将启动线程的工厂模型化为一个服务,这样就能对该服务进行集合化控制了。例如,不对每个任务调用一次execute()方法,ExecutorService能创建一个任务的集合,并可返回代表这些任务未来结果的Future集合。
4. ScheduledExecutorServices
与ExecutorService接口同样优秀,特定的任务需要以计划的形式进行执行,例如在特定的时间间隔或在特定的时刻执行给定的任务。这就是继承自 ExecutorService的ScheduledExecutorService的职责范畴。
如果你的目的是创建一个"心跳"命令,该命令每5秒钟就去"ping"一次。ScheduledExecutorService会帮你做到这一点,正如你在清单4中所见的那般简单:
清单4. ScheduledExecutorService按计划去"Ping"
怎么样?没有操作线程的烦恼,如果用户想取消心跳,也不必操心如何去做,前台或后台都没有显示的标记线程;所有的调度细节都留给了ScheduledExecutorService。
顺便提一下,如果用户想要取消心跳,从scheduleAtFixedRate()方法返回的会是一个ScheduledFuture实例,它不仅含有执行结果(如果有的话),也有一个cancel()方法去停止该计划任务。
5. 超时方法
拥有为阻塞操作置一个确定的超时控制的能力(这样就可以避免死锁)是java.util.concurrent类库相比于旧有并发API,如针对锁的监视器,的最大优点之一。
这些方法几乎总是按int/TimeUnit对的方式进行重载,该int/TimeUnit对用于指示方法在跳出执行并将控制返回给平台之前需要等待多长时间。这要求开发者对此做更多的工作--如果没有获得锁,将如何进行恢复?--但结果却几乎总是正确的:更少的死锁,以及更加生产安全的代码。(更多关于生产就绪的代码,请见Michael Nygard的Release It!)
结论
java.util.concurrent包含有许多更优雅的工具,它们出于集合框架,但更胜之,特别是.locks和.atomic包中的类。深入挖掘之,你将发现像CyclicBarrier这样的十分有用的控制结构,甚至于更多。
下一次,我们将步入一个新的主题:你所不知道的五件关于Jar的事情。
摘要:除了便于编写并发应用的集合API外,java.util.concurrent还引入了其它的预置程序组件,这些组件能辅助你在多线程应用中控制和执行线程。Ted Neward再介绍了五个来自于java.util.concurrent的Java编程必备窍门。
通过提供线程安全,性能良好的数据结构,并发集合框架使并发编程变得更容易。然而在有些情况下,开发者需要多走一步,并要考虑控制和/或调节线程的执行。提供java.util.concurrent包的全部原因就是为了简化多线程编程--事实上正是如此。
接着第一部分,本文介绍了多个同步数据结构,这些数据结构比核心语言基本结构(监视器)的层次要高,但不会高到将它们圈囿在一个集合类中。一旦知道了这些锁与栓的用途,就能径直去用了。
1. 信号量
在有些企业级系统中,常需要开发者去控制针对特定资源的请求(线程或动作)的数量。虽然完全可能试着手工编写这样的调节程序,但使用Semaphore类会更容易些,该类会为你处理对线程的控制,如清单1所示:
清单1. 使用信号量调节线程
import java.util.*;import java.util.concurrent.*; public class SemApp { public static void main(String[] args) { Runnable limitedCall = new Runnable() { final Random rand = new Random(); final Semaphore available = new Semaphore(3); int count = 0; public void run() { int time = rand.nextInt(15); int num = count++; try { available.acquire(); System.out.println("Executing " + "long-running action for " + time + " seconds #" + num); Thread.sleep(time * 1000); System.out.println("Done with #" + num + "!"); available.release(); } catch (InterruptedException intEx) { intEx.printStackTrace(); } } }; for (int i=0; i<10; i++) new Thread(limitedCall).start(); } }
虽然上例有10个线程在运行(针对运行SemApp的Java进程执行jstack程序可以验证这一点),但只有3个是活动的。另外7个线程会被保存起来,直到其中一个信号量计数器被释放出来。(准确地说,Semaphore类支持一次获取和释放一个以上的被许可线程,但在此处的场景中这么做没有意义。)
2. CountDownLatch
如果并发类Semaphore是被设计为在同一时刻允许"其中"一个线程执行的话,那么CountDownLatch就是赛马比赛中的起跑门。该类持有所有的线程,当遇到某个特定条件,那时CountDownLatch就会一次性释放全部的线程。
清单2. CountDownLatch:让我们比赛!
import java.util.*; import java.util.concurrent.*; class Race { private Random rand = new Random(); private int distance = rand.nextInt(250); private CountDownLatch start; private CountDownLatch finish; private List<String> horses = new ArrayList<String>(); public Race(String names) { this.horses.addAll(Arrays.asList(names)); } public void run() throws InterruptedException { System.out.println("And the horses are stepping up to the gate"); final CountDownLatch start = new CountDownLatch(1); final CountDownLatch finish = new CountDownLatch(horses.size()); final List<String> places = Collections.synchronizedList(new ArrayList<String>()); for (final String h : horses) { new Thread(new Runnable() { public void run() { try { System.out.println(h + " stepping up to the gate"); start.await(); int traveled = 0; while (traveled < distance) { // In a 0-2 second period of time. Thread.sleep(rand.nextInt(3) * 1000); // a horse travels 0-14 lengths traveled += rand.nextInt(15); System.out.println(h + " advanced to " + traveled + "!"); } finish.countDown(); System.out.println(h + " crossed the finish!"); places.add(h); } catch (InterruptedException intEx) { System.out.println("ABORTING RACE!!!"); intEx.printStackTrace(); } } }).start(); } System.out.println("And they're off!"); start.countDown(); finish.await(); System.out.println("And we have our winners!"); System.out.println(places.get(0) + " took the gold"); System.out.println(places.get(1) + " got the silver"); System.out.println("and " + places.get(2) + " took home the bronze."); } } public class CDLApp { public static void main(String[] args) throws InterruptedException, java.io.IOException { System.out.println("Prepping"); Race r = new Race( "Beverly Takes a Bath", "RockerHorse", "Phineas", "Ferb", "Tin Cup", "I'm Faster Than a Monkey", "Glue Factory Reject" ); System.out.println("It's a race of " + r.getDistance() + " lengths"); System.out.println("Press Enter to run the race."); System.in.read(); r.run(); } }
注意在清单2中,CountDownLatch服务于两个目的:首先,它同时释放所有的线程,模拟比赛的开始;但之后,另一个CountDownLatch模拟了比赛的结束。一场比赛会有更多的评论,你可以在比赛的"转弯"和"半程"点添加 CountDownLatch,当马匹跑过1/4程,半程和3/4程时。
3. Executor
清单1和清单2中的例子都遭遇了一个令人非常沮丧的错误,你被迫要直接地创建Thread对象。这是一个造成麻烦的方式,因为在有些JVM中,创建 Thread对象是一件重量级的工作,所以重用而非创建新的线程要好得多。然而在另一些JVM中,情况就恰恰相反:Thread是非常轻量级的,若你需要一个线程,直接创建它则会好得多。当然,如果Murphy有他自己的方法(他经常就是这么做的),无论你使用哪种方法,对于你最终所依赖的某种Java平台都会是错误的。
JSR-166专家组在一定程度上预见到了这种情况。与让Java开发者直接创建Thread实例不同,他们推荐Executor接口,这是一个创建新线程的抽象。如果清单3所示,Executor允许你自己不必使用new操作符去创建Thread对象:
清单3. Executor
Executor exec = getAnExecutorFromSomeplace(); exec.execute(new Runnable() { });
使用Excutor的主要缺点与我们使用所有对象工厂所遇到的缺点一样:工厂必须来源于某处。不幸地是,不同于CLR,JVM并不带有一个标准的VM范围内的线程池。
Executor类只是作为获取Executor实现实例的常用地方,但它只有new方法(例如,为了创建新的线程池);它没有预创建的实例。所以,如果你想创建并使用一个能贯穿于整个程序的Executor实现,你就可以创建一个你自己的Executor实例。(或者,在有些情况下,你可以使用你所选容器/平台所提供的Executor实例。)
ExecutorService,为你服务
ExecutorService的用处在于使你不必关心Thread来自于何处,Executor接口缺乏Java开发者可能期望的一些功能,比如启动一个线程,该线程用于产生结果,它会以非阻塞方式一直等待,直到结果出现为止。(在桌面应用中这是很普通的需求,在这种应用中用户会执行一个需要访问数据库的UI操作,如果它耗时太长的话,就可能想要在它完成之前就取消这一操作。)
为此,JSR-166的专家们创造一个更为有用的抽象,ExecutorService接口,该接口将启动线程的工厂模型化为一个服务,这样就能对该服务进行集合化控制了。例如,不对每个任务调用一次execute()方法,ExecutorService能创建一个任务的集合,并可返回代表这些任务未来结果的Future集合。
4. ScheduledExecutorServices
与ExecutorService接口同样优秀,特定的任务需要以计划的形式进行执行,例如在特定的时间间隔或在特定的时刻执行给定的任务。这就是继承自 ExecutorService的ScheduledExecutorService的职责范畴。
如果你的目的是创建一个"心跳"命令,该命令每5秒钟就去"ping"一次。ScheduledExecutorService会帮你做到这一点,正如你在清单4中所见的那般简单:
清单4. ScheduledExecutorService按计划去"Ping"
import java.util.concurrent.*; public class Ping { public static void main(String[] args) { ScheduledExecutorService ses = Executors.newScheduledThreadPool(1); Runnable pinger = new Runnable() { public void run() { System.out.println("PING!"); } }; ses.scheduleAtFixedRate(pinger, 5, 5, TimeUnit.SECONDS); } }
怎么样?没有操作线程的烦恼,如果用户想取消心跳,也不必操心如何去做,前台或后台都没有显示的标记线程;所有的调度细节都留给了ScheduledExecutorService。
顺便提一下,如果用户想要取消心跳,从scheduleAtFixedRate()方法返回的会是一个ScheduledFuture实例,它不仅含有执行结果(如果有的话),也有一个cancel()方法去停止该计划任务。
5. 超时方法
拥有为阻塞操作置一个确定的超时控制的能力(这样就可以避免死锁)是java.util.concurrent类库相比于旧有并发API,如针对锁的监视器,的最大优点之一。
这些方法几乎总是按int/TimeUnit对的方式进行重载,该int/TimeUnit对用于指示方法在跳出执行并将控制返回给平台之前需要等待多长时间。这要求开发者对此做更多的工作--如果没有获得锁,将如何进行恢复?--但结果却几乎总是正确的:更少的死锁,以及更加生产安全的代码。(更多关于生产就绪的代码,请见Michael Nygard的Release It!)
结论
java.util.concurrent包含有许多更优雅的工具,它们出于集合框架,但更胜之,特别是.locks和.atomic包中的类。深入挖掘之,你将发现像CyclicBarrier这样的十分有用的控制结构,甚至于更多。
下一次,我们将步入一个新的主题:你所不知道的五件关于Jar的事情。
发表评论
-
Java 多线程同步问题的探究(三、Lock来了,大家都让开【2. Fair or Unfair? It is a question...】)
2011-04-20 14:46 980让我们继续前面有关ReentrantLock的话题。 首先, ... -
Java 多线程同步问题的探究(三、Lock来了,大家都让开【1. 认识重入锁】)
2011-04-20 14:40 759在上一节中, 我们已经了解了Java多线程编程中常用的关 ... -
Java 多线程同步问题的探究(二、给我一把锁,我能创造一个规矩)
2011-04-20 14:24 816转自:http://www.blogjava.net/zhan ... -
Java多线程同步问题的探究(一、线程的先来后到)
2011-04-20 14:23 779转自:http://www.blogjava.net/zhan ... -
java线程安全总结
2011-04-20 13:55 767转自:http://www.iteye.com ... -
Java线程调度
2011-04-12 22:00 848转自 http://lavasoft.blog.51cto.c ... -
Java线程创建方式
2011-04-12 21:29 8141、定义任务 线程可以驱动任务,因此需要一种描述任务的方式, ... -
Java线程基本概念
2011-04-12 21:12 653进程、线程、并发执行: 在操作系统中两个比较容易混淆的 ... -
What is a Java Thread and How does it work?
2010-10-25 20:48 866A java thread is an ex ... -
你所不知道的五件事情--java.util.concurrent(第一部分)
2010-06-30 15:18 768这是Ted Neward在IBM develope ... -
在Timer和 ScheduledExecutorService间决择
2010-06-30 15:13 789java.util.Timer计时器有管 ...
相关推荐
backport-util-concurrent.jarbackport-util-concurrent.jarbackport-util-concurrent.jar
1. java.util.concurrent - Java 并发工具包 2. 阻塞队列 BlockingQueue 3. 数组阻塞队列 ArrayBlockingQueue 4. 延迟队列 DelayQueue 5. 链阻塞队列 LinkedBlockingQueue 6. 具有优先级的阻塞队列 ...
《深入解析Atlassian Util Concurrent库:0.0.12版本》 在IT行业中,高效且可靠的并发处理是系统性能优化的关键因素之一。Atlassian公司,以其强大的协作工具如Jira、Confluence等闻名,也提供了许多开源工具来支持...
标题中的"apr-util-1.5.4.tar.gz"是一个开源软件库的归档文件,它属于Apache Portable Runtime (APR)项目的一部分。APR是一个为各种操作系统提供统一API的库,主要用于处理底层系统功能,如文件I/O、网络通信、进程...
- copy %AXIS2_HOME%\lib\backport-util-concurrent-3.1.jar 到%ECLIPSE_HOME%\plugins\Axis2_Codegen_Wizard_1.3.0\lib - 注册此 jar 包: 編輯 %ECLIPSE_HOME%\plugins\Axis2_Codegen_Wizard_1.3.0\plugin.xml , ...
An error ocCurred while completing process -java.lang.reflect.InvocationTargetException (1).关闭 Eclipse (2).copy %AXIS2_HOME%\lib\ 下的 backport-util-concurrent-3.1.jar 和 geronimo-stax-api_1.0_...
"java.util.concurrent.ExecutionException: java.lang.OutOfMemoryError" 是一个典型的错误提示,它表明在并发执行过程中遇到了内存不足的问题。下面我们将深入探讨这个问题的原因、影响以及如何解决。 内存溢出...
【标题】"ws-commons-util-1.0.2.zip_ws-comm-util.jar" 提供的是一个名为 ws-commons-util 的库的版本1.0.2,这个库经过压缩打包成ZIP格式,其中包含了 ws-comm-util.jar 文件。这个JAR文件是Java应用程序中常见的...
标签:eclipse、jetty、util、中文文档、jar包、java; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 人性化翻译,文档中的代码和结构保持不变,注释和说明精准翻译,请放心...
2. **配置web.xml**:在你的应用的`WEB-INF/web.xml`文件中,你需要添加一个新的过滤器定义,如下所示: ```xml <filter-name>CORS</filter-name> <filter-class>...
官方版本,亲测可用
protobuf-java-util-3.1.0.jar下载,配合protobuf3.1.jar的util包
本文将详细解析标题和描述中提到的几个关键压缩包:`apr-1.4.6.tar.gz`、`apr-iconv-1.2.1.tar.gz` 和 `apr-util-1.4.1.tar.gz`,这些都是Apache安装的重要组成部分。 首先,`apr-1.4.6.tar.gz` 是APR库的主要版本...
asm-util-1.3.4.jar, asm-util-1.3.5.jar, asm-util-1.4.1.jar, asm-util-1.4.3.jar, asm-util-1.5.1.jar, asm-util-1.5.2.jar, asm-util-1.5.3.jar, asm-util-2.0.jar, asm-util-2.1.jar, asm-util-2.2.1-sources....
标签:eclipse、jetty、util、ajax、中英对照文档、jar包、java; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 人性化翻译,文档中的代码和结构保持不变,注释和说明精准...
asm-util-3.2.jarasm-util-3.2.jarasm-util-3.2.jarasm-util-3.2.jarasm-util-3.2.jarasm-util-3.2.jarasm-util-3.2.jarasm-util-3.2.jarasm-util-3.2.jarasm-util-3.2.jarasm-util-3.2.jarasm-util-3.2.jarasm-util...
backport-util-concurrent项目最初由Doug Lea创建,他是Java并发领域的权威人物,他的贡献包括`java.util.concurrent`包的实现。这个库的核心目标是为那些无法升级到Java 5或更高版本的系统提供线程安全和并发控制的...