你所不知道的五件事情--java.util.concurrent(第二部分)
这是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. 使用信号量调节线程
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.util.concurrent(第二部分)的更多相关文章
- 你所不知道的五件事情--java.util.concurrent(第一部分)
这是Ted Neward在IBM developerWorks中5 things ...
- 聊聊高并发(二十五)解析java.util.concurrent各个组件(七) 理解Semaphore
前几篇分析了一下AQS的原理和实现.这篇拿Semaphore信号量做样例看看AQS实际是怎样使用的. Semaphore表示了一种能够同一时候有多个线程进入临界区的同步器,它维护了一个状态表示可用的票 ...
- 【译】Surface中你也许不知道的五件事
Bring up the Quick Link Menu - Select the Windows Key + X or right click the Start Button to bring u ...
- 关于 java.util.concurrent 您不知道的 5 件事--转
第 1 部分 http://www.ibm.com/developerworks/cn/java/j-5things4.html Concurrent Collections 是 Java™ 5 的巨 ...
- 你所不知道的库存超限做法 服务器一般达到多少qps比较好[转] JAVA格物致知基础篇:你所不知道的返回码 深入了解EntityFramework Core 2.1延迟加载(Lazy Loading) EntityFramework 6.x和EntityFramework Core关系映射中导航属性必须是public? 藏在正则表达式里的陷阱 两道面试题,带你解析Java类加载机制
你所不知道的库存超限做法 在互联网企业中,限购的做法,多种多样,有的别出心裁,有的因循守旧,但是种种做法皆想达到的目的,无外乎几种,商品卖的完,系统抗的住,库存不超限.虽然短短数语,却有着说不完,道不 ...
- (转)关于 Java 对象序列化您不知道的 5 件事
关于 Java 对象序列化您不知道的 5 件事 转自:http://developer.51cto.com/art/201506/479979.htm 数年前,当和一个软件团队一起用 Java 语言编 ...
- 关于 Java Collections API 您不知道的 5 件事,第 1 部分
定制和扩展 Java Collections Java™ Collections API 远不止是数组的替代品,虽然一开始这样用也不错.Ted Neward 提供了关于用 Collections 做更 ...
- 关于 Java Collections API 您不知道的 5 件事--转
第 1 部分 http://www.ibm.com/developerworks/cn/java/j-5things2.html 对于很多 Java 开发人员来说,Java Collections A ...
- 你所不知道的html5与html中的那些事(五)——web图像
文章简介: 现在的页面,一般都离不开图像,而怎么做才能让我们的页面中的图像加载的又快又好呢?在优化页面速度的时候还有什么事是你所不知道的呢? 下面看看今天我为大家带来了哪些关于we ...
随机推荐
- Spring/Hibernate 应用性能优化的7种方法
对于大多数典型的 Spring/Hibernate 企业应用而言,其性能表现几乎完全依赖于持久层的性能.此篇文章中将介绍如何确认应用是否受数据库约束,同时介绍七种常用的提高应用性能的速成法.本文系 O ...
- 【疯狂Java讲义学习笔记】【数据类型与运算符】
[学习笔记]1.8bit = 1byte,4byte = 1word.Java中的整型数据有byte(1字节),short(2字节),int(4字节),long(8字节).Java中的浮点数据有flo ...
- highcharts 切换
<!doctype html> <html lang="en"> <head> <script type="text/javas ...
- red5研究(一):下载,工程建立、oflaDemo安装、demo测试
一.red5下载.添加工程到myeclipse 1,从官网上下载red51.01版本(我下载的是red51.0的版本),下载链接http://www.red5.org/downloads/red5/1 ...
- iOS开发--绘图教程
本文是<Programming iOS5>中Drawing一章的翻译,考虑到主题完整性,翻译版本中加入了一些书中未涉及到的内容.希望本文能够对你有所帮助. 本文由海水的味道翻译整理,转载请 ...
- Android性能优化典范 - 第2季
Google发布了Android性能优化典范第2季的课程,一共20个短视频,包括的内容大致有:电量优化,网络优化,Wear上如何做优化,使用对象池来提高效率,LRU Cache,Bitmap的缩放,缓 ...
- python 操作符笔记:
操作符 描述 x if y else z 三元描述(2.5新加)(类似于c中的 x?y:z x or y 或 x and y 与 not x 非 x!=y ,x is y 序列成员测试 x | y 位 ...
- 【HDOJ】4363 Draw and paint
看题解解的.将着色方案映射为40*40*5*5*5*5*2个状态,40*40表示n*m,5*5*5*5表示上下左右相邻块的颜色,0表示未着色.2表示横切或者竖切.基本思路是记忆化搜索然后去重,关键点是 ...
- Android开发之实用小知识点汇总-2
1.EditText 中将光标移到文字末尾: EditText mEdit = (EditText)this.findViewById(R.id.EditText01); mEdit .setText ...
- 移动端调试 weinre
weinre 是基于 Node 的工具,因此使用如下命令安装 weinre $ npm install -g weinre 用上面的命令将 weinre 安装到全局,然后就可以使用 weinre的命令 ...