?工作U程---WorkerThread 思义 它本w就是一个线E,而且是专门用来工作的Q工作线E的主要d是从d队列中取ZQ?然后执行d
?d队列---TaskQueue FIFO数据l构 在出对入队的时候要锁定对象避免两个U程重复处理某Q?
在这里我采用的是java提供的ConcurrentLinkedQueue队列Q这是一个用链表实现的队 可无限的扩大Q具体用法请看doc
用到队列的地方主要有两个 addTask(Task task) ?Task task = queue.poll();
?d接口---Task d接口只有一个方?executeQ)(j)Q用者只需实现q个接口可以了(jin)
用法Q?/p>
可以看出用户的用非常简?
在jdk5?java提供?jin)线E池
有一Ҏ(gu)?千万不要在servlet中调用线E池 因ؓ(f)servlet本来是一个线E池
![]() |
服务器程序利用线E技术响应客戯求已l司I?可能(zhn)认样做效率已经很高Q但(zhn)有没有惌优化一下用线E的Ҏ(gu)。该文章向(zhn)介l服务器E序如何利用U程池来优化性能q提供一个简单的U程池实现?/p>
在面向对象编E中Q创建和销毁对象是很费旉的,因ؓ(f)创徏一个对象要获取内存资源或者其它更多资源。在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后q行垃圾回收。所以提高服务程序效率的一个手D就是尽可能减少创徏和销毁对象的ơ数Q特别是一些很耗资源的对象创徏和销毁。如何利用已有对象来服务是一个需要解决的关键问题Q其实这是一?池化资源"技术生的原因。比如大家所熟?zhn)的数据库q接池正是遵循这一思想而生的Q本文将介绍的线E池技术同L(fng)合这一思想?/p> 目前Q一些著名的大公叔R特别看好q项技术,q早已经在他们的产品中应用该技术。比如IBM的WebSphereQIONA的Orbix 2000在SUN?Jini中,Microsoft的MTSQMicrosoft Transaction Server 2.0Q,COM+{?/p> 现在(zhn)是否也惛_服务器程序应用该Ҏ(gu)?
我所提到服务器程序是指能够接受客戯求ƈ能处理请求的E序Q而不只是指那些接受网l客戯求的|络服务器程序?/p> 多线E技术主要解军_理器单元内多个线E执行的问题Q它可以显著减少处理器单元的闲置旉Q增加处理器单元的吞吐能力。但如果对多U程应用不当Q会(x)增加对单个Q务的处理旉。可以D一个简单的例子Q?/p> 假设在一台服务器完成一Q务的旉为T T1 创徏U程的时? T2 在线E中执行d的时_(d)包括U程间同步所需旉 T3 U程销毁的旉 昄T Q?T1QT2QT3。注意这是一个极度简化的假设?/p> 可以看出T1,T3是多U程本n的带来的开销Q我们(f)望减T1,T3所用的旉Q从而减T的时间。但一些线E的使用者ƈ没有注意到这一点,所以在E序中频J的创徏或销毁线E,q导致T1和T3在T中占有相当比例。显然这是突Z(jin)U程的弱点(T1QT3Q,而不是优点(q发性)(j)?/p> U程池技术正是关注如何羃短或调整T1,T3旉的技术,从而提高服务器E序性能的。它把T1QT3分别安排在服务器E序的启动和l束的时间段或者一些空闲的旉D,q样在服务器E序处理客户hӞ不会(x)有T1QT3的开销?jin)?/p> U程池不仅调整T1,T3产生的时间段Q而且它还显著减少?jin)创建线E的数目。在看一个例子:(x) 假设一个服务器一天要处理50000个请求,q且每个h需要一个单独的U程完成。我们比较利用线E池技术和不利于线E池技术的服务器处理这些请求时所产生的线EL。在U程池中Q线E数一般是固定的,所以生线EL不会(x)过U程池中U程的数目或者上限(以下U线E池寸Q,而如果服务器不利用线E池来处理这些请求则U程L?0000。一般线E池寸是远于50000。所以利用线E池的服务器E序不会(x)Z(jin)创徏50000而在处理h时浪Ҏ(gu)_(d)从而提高效率?/p> q些都是假设Q不能充分说明问题,下面我将讨论U程池的单实现ƈ对该E序q行Ҏ(gu)试Q以说明U程技术优点及(qing)应用领域?/p>
一般一个简单线E池臛_包含下列l成部分?/p>
U程池管理器臛_有下列功能:(x)创徏U程池,销毁线E池Q添加新d创徏U程池的部分代码如下Q?/p>
注意同步workThreadVectorq没有降低效率,相反提高?sh)(jin)效?请参考Brian Goetz的文章?销毁线E池的部分代码如下:(x)
dCQ务的部分代码如下Q?/p>
工作U程是一个可以@环执行Q务的U程Q在没有d时将{待。由于代码比较多在此不罗? d接口是ؓ(f)所有Q务提供统一的接口,以便工作U程处理。Q务接口主要规定了(jin)d的入口,d执行完后的收ַ作,d的执行状态等。在文章l尾有相关代码的下蝲?/p> 以上所描述的线E池l构很简单,一些复杂的U程池结构将不再此讨论?/p> 在下载代码中有测试驱动程序(TestThreadPoolQ,我利用这个测试程序的输出数据l计Z列测试结果。测试有两个参数要设|:(x)
分别一个参数固定,另一个参数变动以考察两个参数所产生的不同结果。所用测试机器分别ؓ(f)普通PC机(Win2000 JDK1.3.1Q和SUN服务?Solaris Unix JDK1.3.1)Q机器配|在此不便指明?/p>
?.U程池的寸的对服务器程序的性能影响 ![]() Ҏ(gu)以上l计数据可得Z图:(x) ?.d数对服务器程序的冲击 ![]() 数据分析如下Q?/p> ?是改变线E池寸Ҏ(gu)务器性能的媄(jing)响,在该试q程中,服务器的要完成的d数固定ؓ(f)?000。从?中可以看出合理配|线E池寸对于大量d处理的效率有非常明显的提高,但是一旦尺寔R择不合理(q大或过)(j)׃(x)严重降低影响服务器性能。理Z"q小"出CQ务不能及(qing)时处理的情况,但在图表中显C出某些尺寸的U程池表现很好,q是因ؓ(f)试驱动中有很多U程同步开销Q且q个开销相对于完成单个Q务的旉是不能忽略的?q大"则会(x)出现U程间同步开销太大的问题,而且在线E间切换很耗CPU旉Q在图表昄的很清楚。可见Q何一个好技术,如果滥用都会(x)造成N性后果?/p> ?是用不同数量的Q务来冲击服务器程序,在该试q程中,服务器线E池寸固定?6。可以看出线E池在处理少量Q务时的优势不明显。所以线E池技术有一定的适应范围Q关于适用范围在后面讨论。但对于大量的Q务的处理Q线E池的优势表现非常卓,服务器程序处理请求的旉虽然有L动,但是其^均值相对小多了(jin)?/p> 值得注意的是试Ҏ(gu)中,l计d的完成时间没有包含了(jin)创徏U程池的旉。在实际U程池工作时Q即利用U程池处理Q务时Q创建线E池的时间是不必计算在内的?/p> ׃试驱动E序有很多同步代码,特别是等待线E执行完毕的同步Q代码中为sleepToWait(long l)Ҏ(gu)的调用)(j)Q这些代码降低了(jin)代码执行效率Q这是测试驱动一个缺点,但这个测试驱动可以说明线E池相对于简单用线E的优势?/p>
单线E池存在一些问题,比如如果有大量的客户要求服务器ؓ(f)其服务,但由于线E池的工作线E是有限的,服务器只能ؓ(f)部分客户服务Q其它客h交的dQ只能在d队列中等待处理。一些系l设计h员可能会(x)不满q种状况Q因Z们对服务器程序的响应旉要求比较严格Q所以在pȝ设计时可能会(x)怀疑线E池技术的可行性,但是U程池有相应的解x(chng)案。调整优化线E池寸是高U线E池要解决的一个问题。主要有下列解决Ҏ(gu)Q?/p>
在一些高U线E池中一般提供一个可以动态改变的工作U程数目的功能,以适应H发性的h。一旦请求变了(jin)逐步减少U程池中工作U程的数目。当然线E增加可以采用一U超前方式,x(chng)量增加一批工作线E,而不是来一个请求才建立创徏一个线E。批量创建是更加有效的方式。该Ҏ(gu)q有应该限制U程池中工作U程数目的上限和下限。否则这U灵zȝ方式也就变成一U错误的方式或者灾难,因ؓ(f)频繁的创建线E或者短旉内生大量的U程会(x)背离使用U程池原始初?-减少创徏U程的次数?/p> 举例QJini中的TaskManagerQ就是一个精巧线E池理器,它是动态增加工作线E的。SQL Server采用单进E?Single Process)多线E?Multi-Thread)的系l结构,1024个数量的U程池,动态线E分配,理论上限32767?/p>
如果不想在线E池应用复杂的策略来保证工作U程数满_用的要求Q你pҎ(gu)l计学的原理来统计客L(fng)h数目Q比如高峰时D^均一U钟内有多少d要求处理QƈҎ(gu)pȝ的承受能力及(qing)客户的忍受能力来q估计一个合理的U程池尺寸。线E池的尺寸确实很隄定,所以有时干脆用l验倹{?/p> 举例Q在MTS中线E池的尺寸固定ؓ(f)100?/p>
在一些复杂的pȝl构?x)采用这个方案。这样可以根据不同Q务或者Q务优先来采用不同线E池处理?/p> 举例QCOM+用到?jin)多个线E池?/p> q三U方案各有优~点。在不同应用中可能采用不同的Ҏ(gu)或者干脆组合这三种Ҏ(gu)来解军_际问题?/p>
下面是我ȝ的一些线E池应用范围,可能是不全面的?/p> U程池的应用范围Q?/p>
本文只是单介l线E池技术。可以看出线E池技术对于服务器E序的性能改善是显著的。线E池技术在服务器领域有着q泛的应用前景。希望这Ҏ(gu)术能够应用到(zhn)的多线E服务程序中?/p> |
回复(1):
4.2 框架与结?br />下面让我们来看看util.concurrent的框架结构。关于这个工具包概述的e文原版链接地址是http: //gee.cs.oswego.edu/dl/cpjslides/util.pdf。该工具包主要包括三大部分:(x)同步、通道和线E池执行器。第一部分 主要是用来定刉Q资源管理,其他的同步用途;通道则主要是为缓冲和队列服务的;U程池执行器则提供了(jin)一l完善的复杂的线E池实现?br />--主要的结构如下图所C?br />
4.2.1 Sync
acquire/release协议的主要接?br />- 用来定制锁,资源理Q其他的同步用?br />- 高层抽象接口
- 没有区分不同的加锁用?br />
实现
-Mutex, ReentrantLock, Latch, CountDown,Semaphore, WaiterPreferenceSemaphore, FIFOSemaphore, PrioritySemaphore
q有Q有几个单的实现Q例如ObservableSync, LayeredSync
举例Q如果我们要在程序中获得一独占锁,可以用如下简单方式:(x)
try {
lock.acquire();
try {
action();
}
finally {
lock.release();
}
}catchQException eQ{
}
E序?使用lock对象的acquire()Ҏ(gu)获得一独占锁,然后执行(zhn)的操作Q锁用完后,使用release()Ҏ(gu)释放之即可。呵呵,单吧Q想 想看Q如果?zhn)亲自撰写独占锁,大概会(x)考虑到哪些问题?如果关键的锁得不到怎末办?用v来是不是?x)复杂很多?而现在,以往的很多细节和Ҏ(gu)异常情况在这里都 无需多考虑Q?zhn)可以把_֊花在解决(zhn)的应用问题?sh)去?br />
4.2.2 通道(Channel)
为缓Ԍ队列{服务的L?br />
具体实现
LinkedQueue, BoundedLinkedQueue,BoundedBuffer, BoundedPriorityQueue, SynchronousChannel, Slot
通道例子
class Service { // ...
final Channel msgQ = new LinkedQueue();
public void serve() throws InterruptedException {
String status = doService();
msgQ.put(status);
}
public Service() { // start background thread
Runnable logger = new Runnable() {
public void run() {
try {
for(;;)
System.out.println(msqQ.take());
}
catch(InterruptedException ie) {} }
};
new Thread(logger).start();
}
}
在后台服务器中,~冲和队列都是最常用到的。试惻I如果Ҏ(gu)有远端的h不排个队列,让它们一拥而上的去争夺cpu、内存、资源,那服务器瞬间不当掉才怪。而在q里Q成熟的队列和缓冲实现已l提供,(zhn)只需要对其进行正初始化q用即可,大大~短?jin)开发时间?br />
4.2.3执行?Executor)
Executor是这里最重要、也是我们往往最l写E序要用到的Q下面重点对其进行介l?br />cMU程的类的主接口
- U程?br />- 轻量U运行框?br />- 可以定制调度法
只需要支持execute(Runnable r)
- 同Thread.startcM
实现
- PooledExecutor, ThreadedExecutor, QueuedExecutor, FJTaskRunnerGroup
PooledExecutorQ线E池执行器)(j)是个最常用到的c,以它ZQ?br />可修改得属性如下:(x)
- d队列的类?br />- 最大线E数
- 最线E数
- 预热(预分?和立?分配)U程
- 保持z跃直到工作U程l束
-- 以后如果需要可能被一个新的代?br />- 饱和(Saturation)协议
-- dQ丢弃,生者运行,{等
可不要小看上面这数条属性,对这些属性的讄完全可以{同于?zhn)自己撰写的线E池的成百上千行代码。下面以W者撰写过得一个GIS服务器ؓ(f)例:(x)
该GIS服务器是一个典型的“请求-服务”类型的服务器,遵@后端E序设计的一般框架。首先对所有的h按照先来先服务排入一个请求队列,如果瞬间到达?h过?jin)请求队列的定wQ则溢出的h转移至一个(f)旉列。如果(f)旉列也排满?jin),则对以后辑ֈ的请求给予一个“服务器忙”的提示后将其简单抛弃。这 个就够忙zM늚?jin)?br />然后Q结合链表结构实C个线E池Q给池一个初始容量。如果该池满Q以x2的策略将池的定w动态增加一倍,依此cLQ直到ȝE数服务辑ֈpȝ能力上限Q?之后U程池容量不在增加,所有请求将{待一个空余的q回U程。每从池中得C个线E,该线E就开始最hq行GIS信息的服务,如取坐标、取地图Q等{?服务完成后,该线E返回线E池l箋(hu)求队列离地后l请求服务,周而复始。当时用矢量链表来暂存请求,用wait()?notify() ?synchronized{原语结合矢量链表实现线E池QdU?00行程序,而且在运行时间较长的情况下服务器不稳定,U程池被取用的线E有异常消失?情况发生。而用util.concurrent相关cM后,仅用?jin)几十行E序完成了(jin)相同的工作而且服务器运行稳定,U程池没有丢qE的情况发生。由 此可见util.concurrent包极大的提高?sh)(jin)开发效率,为项目节省了(jin)大量的时间?br />使用PooledExecutor例子
import java.net.*;
/**
* Title:
* (tng)Description: 负责初始化线E池以及(qing)启动服务?/font>
* Copyright: Copyright (c) 2003
* Company:
* @author not attributable
* @version 1.0
*/
public class MainServer {
//初始化常?br />public static final int MAX_CLIENT=100; //pȝ最大同时服务客h
//初始化线E池
public static final PooledExecutor pool =
new PooledExecutor(new BoundedBuffer(10), MAX_CLIENT); //chanel定w?0,
//在这里ؓ(f)U程池初始化?jin)一?br />//长度?0的Q务缓冲队列?br />
public MainServer() {
//讄U程池运行参?br />pool.setMinimumPoolSize(5); //讄U程池初始容量ؓ(f)5个线E?br />pool.discardOldestWhenBlocked();//对于出队列的请求,使用?jin)抛弃策略?br />pool.createThreads(2); //在线E池启动的时候,初始化了(jin)h一定生命周期的2个“热”线E?br />}
public static void main(String[] args) {
MainServer MainServer1 = new MainServer();
new HTTPListener().start();//启动服务器监听和处理U程
new manageServer().start();//启动理U程
}
}
cHTTPListener
import java.net.*;
/**
* Title:
* Description: 负责监听端口以及(qing)Q务交l线E池处理
* Copyright: Copyright (c) 2003
* Company:
* @author not attributable
* @version 1.0
*/
public class HTTPListener extends Thread{
public HTTPListener() {
}
public void run(){
try{
ServerSocket server=null;
Socket clientconnection=null;
server = new ServerSocket(8008);//服务套接字监听某地址端口?br />while(true){//无限循环
clientconnection =server.accept();
System.out.println("Client connected in!");
//使用U程池启动服?br />MainServer.pool.execute(new HTTPRequest(clientconnection));//如果收到一个请求,则从U程池中取一个线E进行服务,d完成后,该线E自动返q线E池
}
}catch(Exception e){
System.err.println("Unable to start serve listen:"+e.getMessage());
e.printStackTrace();
}
}
}
关于util.concurrent工具包就有选择的介l到q,更详l的信息可以阅读q些java源代码的API文档。Doug Lea是个很具有“open”精的作者,他将util.concurrent工具包的java源代码全部公布出来,有兴的读者可以下载这些源代码q细 l品呟?
5 l束?br />以上内容介绍?jin)线E池基本原理以及(qing)设计后台服务E序应考虑到的问题Qƈl合实例详细介绍?jin)重要的多线E开发工具包util.concurrent的构架和使用。结合用已有完善的开发包Q后端服务程序的开发周期将大大~短Q同时程序性能也有?jin)保障?br />
参考文?br />[1] Chad Darby,etc. 《Beginning Java Networking? ?sh)子工业出版C? 2002q??
[2] util.concurrent 说明文g http://gee.cs.oswego.edu/dl/cpjslides/util.pdf
[3] q勇.U程池的介绍?qing)简单实?http://www-900.ibm.com/developerWorks/cn/java/l-threadPool/index.shtml
2002q??br />[4]BrianGoetz. 我的U程到哪里去?jin)http://www-900.cn.ibm.com/developerworks/cn/java/j-jtp0924/index.shtml
Util.concurrent工具包概q?/strong>
Doug Lea
State University of New York at Oswego
dl@cs.oswego.edu
http://gee.cs.oswego.edu
译Q?/p>
Cocia Lin(cocia@163.com)
Huihoo.org
原文
http://gee.cs.oswego.edu/dl/cpjslides/util.pdf
要点
–目标和l构
–主要的接口和实?/p>
Sync:获得/释放(acquire/release) 协议
Channel:攄/取走(put/take) 协议
Executor:执行Runnabled
–每一个部分都有一些关联的接口和支持类
–简单的涉及(qing)其他的类和特?/p>
目标
–一些简单的接口
-但是覆盖大部分程序员需要小?j)处理代码的问?/p>
?高质量实?/p>
-正确的,保守的,有效率的Q可UL?/p>
–可能作为将来标准的基础
-获取l验和收集反馈信?/p>
Sync
?acquire/release协议的主要接?/p>
-用来定制锁,资源理Q其他的同步用?/p>
- 高层抽象接口
- 没有区分不同的加锁用?/p>
–实?/p>
-Mutex, ReentrantLock, Latch, CountDown,Semaphore, WaiterPreferenceSemaphore,FIFOSemaphore, PrioritySemaphore
n q有Q有几个单的实现Q例如ObservableSync, LayeredSync
独占?br />try {
lock.acquire();
try {
action();
}
finally {
lock.release();
}
}
catch (InterruptedException ie) { ... }
?Java同步块不适用的时候用它
- 时Q回退(back-off)
- 保可中?/p>
- 大量q速锁?/p>
- 创徏Posix风格应用(condvar)
独占例子class ParticleUsingMutex {
int x; int y;
final Random rng = new Random();
final Mutex mutex = new Mutex();
public void move() {
try {
mutex.acquire();
try { x += rng.nextInt(2)-1; y += rng.nextInt(2)-1; }
finally { mutex.release(); }
}
catch (InterruptedException ie) {
Thread.currentThread().interrupt(); }
}
public void draw(Graphics g) {
int lx, ly;
try {
mutex.acquire();
try { lx = x; ly = y; }
finally { mutex.release(); }
}
catch (InterruptedException ie) {
Thread.currentThread().interrupt(); return; }
g.drawRect(lx, ly, 10, 10);
}
}
回退(Backoff)例子class CellUsingBackoff {
private long val;
private final Mutex mutex = new Mutex();
void swapVal(CellUsingBackoff other)
throws InterruptedException {
if (this == other) return; // alias check
for (;;) {
mutex.acquire();
try {
I f (other.mutex.attempt(0)) {
try {
long t = val;
val = other.val;
other.val = t;
return;
}
finally { other.mutex.release(); }
}
}
finally { mutex.release(); };
Thread.sleep(100); // heuristic retry interval
}
}
}
d?br />interface ReadWriteLock {
Sync readLock();
Sync writeLock();
}
?理一寚w
- 和普通的锁一L(fng)使用?fn)?/p>
?寚w合类很有?/p>
-半自动的方式实现SyncSet, SyncMap, ?/p>
?实现者用不同的锁策?/p>
- WriterPreference, ReentrantWriterPreference,
ReaderPreference, FIFO
ReadWriteLock例子
?C在读写锁中执行Q何Runnable的包装类
class WithRWLock {
final ReadWriteLock rw;
public WithRWLock(ReadWriteLock l) { rw = l; }
public void performRead(Runnable readCommand)
throws InterruptedException {
rw.readLock().acquire();
try { readCommand.run(); }
finally { rw.readlock().release(); }
}
public void performWrite(? // similar
}
闭锁(Latch)
?闭锁是开始时讄为false,但一旦被讄为trueQ他永q保持true状?/p>
- 初始化标?/p>
- 结束定?/p>
- U程中断
- 事g出发指示?/p>
?CountDown和他有点cMQ不同的是,CountDown需要一定数量的触发讄Q而不是一?/p>
?非常单,但是q泛使用的类
- 替换Ҏ(gu)犯错的开发代?/p>
Latch Example 闭锁例子
class Worker implements Runnable {
Latch startSignal;
Worker(Latch l) { startSignal = l; }
public void run() {
startSignal.acquire();
// ?doWork();
}
}
class Driver { // ?/p>
void main() {
Latch ss = new Latch();
for (int i = 0; i < N; ++i) // make threads
new Thread(new Worker(ss)).start();
doSomethingElse(); // don’t let run yet
ss.release(); // now let all threads proceed
}
}
信号(Semaphores)
-- 服务于数量有限的占有?/p>
- 使用许可数量构造对?通常?)
- 如果需要一个许可才能获取,{待Q然后取C个许?/p>
- 释放的时候将许可d回来
-- 但是真正的许可ƈ没有转移(But no actual permits change hands.)
- 信号量仅仅保留当前的计数?/p>
-- 应用E序
- 锁:(x)一个信号量可以被用作互斥体(mutex)
- 一个独立的{待~存或者资源控制的操作
- 设计pȝ是想忽略底层的系l信?/p>
-- (phores ‘remember?past signals)C已经消失的信号量
信号量例?br />class Pool {
ArrayList items = new ArrayList();
HashSet busy = new HashSet();
final Semaphore available;
public Pool(int n) {
available = new Semaphore(n);
// ?somehow initialize n items ?
}
public Object getItem() throws InterruptedException {
available.acquire();
return doGet();
}
public void returnItem(Object x) {
if (doReturn(x)) available.release();
}
synchronized Object doGet() {
Object x = items.remove(items.size()-1);
busy.add(x); // put in set to check returns
return x;
}
synchronized boolean doReturn(Object x) {
return busy.remove(x); // true if was present
}
}
屏障(Barrier)
?多部分同步接?/p>
- 每一部分都必ȝ待其他的分不撞倒屏?/p>
?CyclicBarrierc?/p>
- CountDown的一个可以重新设|的版本
- 对于反复划分法很有?iterative partitioning algorithms)
?Rendezvousc?/p>
- 一个每部分都能够和其他部分交换信息的屏?/p>
- 行ؓ(f)cM同时的在一个同步通道上put和take
- 对于资源交换协议很有?resource-exchange protocols)
通道(Channel)
–ؓ(f)~冲Q队列等服务的主接口
?具体实现
- LinkedQueue, BoundedLinkedQueue,BoundedBuffer, BoundedPriorityQueue,SynchronousChannel, Slot
通道属?br />?被定义ؓ(f)Puttable和Takable的子接口
- 允许安装生?消费者模式执?/p>
?支持可超时的操作offer和poll
- 当超时值是0Ӟ可能?x)被d
- 所有的Ҏ(gu)能够抛出InterruptedException异常
?没有接口需要sizeҎ(gu)
- 但是一些实现定义了(jin)q个Ҏ(gu)
- BoundedChannel有capacityҎ(gu)
通道例子
class Service { // ?/p>
final Channel msgQ = new LinkedQueue();
public void serve() throws InterruptedException {
String status = doService();
msgQ.put(status);
}
public Service() { // start background thread
Runnable logger = new Runnable() {
public void run() {
try {
for(;;)
System.out.println(msqQ.take());
}
catch(InterruptedException ie) {} }
};
new Thread(logger).start();
}
}
q行?Executor)
?cMU程的类的主接口
- U程?/p>
- 轻量U运行框?/p>
- 可以定制调度法
?只需要支持execute(Runnable r)
- 同Thread.startcM
?实现
- PooledExecutor, ThreadedExecutor,QueuedExecutor, FJTaskRunnerGroup
- 相关的ThreadFactorycd许大多数的运行器通过定制属性用线E?/p>
PooledExecutor
?一个可调的工作者线E池Q可修改得属性如下:(x)
- d队列的类?/p>
- 最大线E数
- 最线E数
- 预热(预分?和立?分配)U程
- 保持z跃直到工作U程l束
?以后如果需要可能被一个新的代?/p>
- 饱和(Saturation)协议
?dQ丢弃,生者运行,{等
PooledExecutor例子
class WebService {
public static void main(String[] args) {
PooledExecutor pool =
new PooledExecutor(new BoundedBuffer(10), 20);
pool.createThreads(4);
try {
ServerSocket socket = new ServerSocket(9999);
for (;;) {
final Socket connection = socket.accept();
pool.execute(new Runnable() {
public void run() {
new Handler().process(connection);
}});
}
}
catch(Exception e) { } // die
}
}
class Handler { void process(Socket s); }
前景(Future)和可调用(Callable)
?Callabe是类gRunnable的接口,用来作ؓ(f)参数和传递结?/p>
interface Callable {
Object call(Object arg) throws Exception;
}
?FutureResult理Callable的异步执?/p>
class FutureResult { // ?/p>
// block caller until result is ready
public Object get()
throws InterruptedException, InvocationTargetException;
public void set(Object result); // unblocks get
// create Runnable that can be used with an Executor
public Runnable setter(Callable function);
}
FutureResult例子
class ImageRenderer { Image render(byte[] raw); }
class App { // ?/p>
Executor executor = ? // any executor
ImageRenderer renderer = new ImageRenderer();
public void display(byte[] rawimage) {
try {
FutureResult futureImage = new FutureResult();
Runnable cmd = futureImage.setter(new Callable(){
public Object call() {
return renderer.render(rawImage);
}});
executor.execute(cmd);
drawBorders(); // do other things while executing
drawCaption();
drawImage((Image)(futureImage.get())); // use future
}
catch (Exception ex) {
cleanup();
return;
}
}
}
其他的类
?CopyOnWriteArrayList
- 支持整个集合复制时每一个修改的无锁讉K
- 适合大多数的多\q播应用E序
?工具包还包括?jin)一个java.beans多\q播cȝCOW版本
?SynchronizedDouble, SynchronizedInt,SynchronizedRef, etc
- cM于java.lang.DoubleQ提供可变操作的同步版本.例如QaddTo,inc
- d?jin)一些象swap,commitq样的实用操?/p>
未来计划
?q发数据构架
- 一l繁重线E连接环境下有用的工具集?/p>
–支持侧重I/O的程?/p>
- 事g机制的IOpȝ
?版本的实现
- 例如SingleSourceQueue
–小q度的改?/p>
- 使运行器更容易?/p>
?替换
- JDK1.3 java.util.Timer 被ClockDaemon取代
在了(jin)解Java的同步秘密之前,先来看看JMM(Java Memory Model)?
Java被设计ؓ(f)跨^台的语言Q在内存理上,昄也要有一个统一的模型。而且Java语言最大的特点是废除?jin)指针,把程序员(sh)痛苦中解脱出来Q不用再考虑内存?sh)用和管理方面的问题?
可惜世事M如人意Q虽然JMM设计上方便了(jin)E序员,但是它增加了(jin)虚拟机的复杂E度Q而且q导致某些编E技巧在Java语言中失效?
JMM主要是ؓ(f)?jin)规定?jin)U程和内存(sh)间的一些关pR对JavaE序员来说只需负责用synchronized同步关键字,其它诸如与线E?内存?sh)间q行数据交换/同步{繁琐工作均p拟机负责完成。如?所C:(x)Ҏ(gu)JMM的设计,pȝ存在一个主内存(Main Memory)QJava中所有变量都储存在主存(sh)Q对于所有线E都是共享的。每条线E都有自q工作内存(Working Memory)Q工作内存(sh)保存的是d?sh)某些变量的拯Q线E对所有变量的操作都是在工作内存(sh)q行Q线E之间无法相互直接访问,变量传递均需要通过d完成?
? Java内存模型CZ?
U程若要Ҏ(gu)变量q行操作Q必ȝq一pd步骤Q首先从d复制/h数据到工作内存,然后执行代码Q进行引?赋值操作,最后把变量内容写回Main Memory。Java语言规范(JLS)中对U程和主存(sh)操作定义?个行为,分别为loadQsaveQreadQwriteQassign和useQ这些操作行为具有原子性,且相互依赖,有明的调用先后序。具体的描述请参见JLSW?7章?
我们在前面的章节介绍?jin)synchronized的作用,现在Q从JMM的角度来重新审视synchronized关键字?
假设某条U程执行一个synchronized代码D,光Ҏ(gu)变量q行操作QJVM?x)依ơ执行如下动作?x)
(1) 获取同步对象monitor (lock)
(2) 从主存复制变量到当前工作内存 (read and load)
(3) 执行代码Q改变共享变量?(use and assign)
(4) 用工作内存数据刷C存相兛_?(store and write)
(5) 释放同步对象?(unlock)
可见Qsynchronized的另外一个作用是保证d内容和线E的工作内存?sh)的数据的一致性。如果没有用synchronized关键字,JVM不保证第2步和W?步会(x)严格按照上述ơ序立即执行。因为根据JLS中的规定Q线E的工作内存和主存(sh)间的数据交换是松耦合的,什么时候需要刷新工作内存或者更C内存内容Q可以由具体的虚拟机实现自行军_。如果多个线E同时执行一D|lsynchronized保护的代码段Q很有可能某条线E已l改动了(jin)变量的|但是其他U程却无法看到这个改动,依然在旧的变量gq行q算Q最l导致不可预料的q算l果?
q一节我们要讨论的是一个让Java丢脸的话题:(x)DCL失效。在开始讨Z前,先介l一下LazyLoadQ这U技巧很常用Q就是指一个类包含某个成员变量Q在cd始化的时候ƈ不立即ؓ(f)该变量初始化一个实例,而是{到真正要用到该变量的时候才初始化之?
例如下面的代码:(x)
代码1
class Foo {
private Resource res = null ;
public Resource getResource() {
if (res == null )
res = new Resource();
return res;
}
}
׃LazyLoad可以有效的减系l资源消耗,提高E序整体的性能Q所以被q泛的用,qJava的缺省类加蝲器也采用q种Ҏ(gu)来加载JavacR?
在单U程环境下,一切都相安无事Q但如果把上面的代码攑ֈ多线E环境下q行Q那么就可能?x)出现问题。假设有2条线E,同时执行C(jin)if(res == null)Q那么很有可能res被初始化2ơ,Z(jin)避免q样的Race ConditionQ得用synchronized关键字把上面的方法同步v来。代码如下:(x)
代码2
Class Foo {
Private Resource res = null ;
Public synchronized Resource getResource() {
If (res == null )
res = new Resource();
return res;
}
}
现在Race Condition解决?jin),一切都很好?
N天过后,好学的你偶然看了(jin)一本Refactoring的魔书,深深Z打动Q准备自己尝试这重构一些以前写q的E序Q于是找C(jin)上面q段代码。你已经不再是以前的Java菜鸟Q深知synchronizedq的Ҏ(gu)在速度上要比未同步的方法慢?00倍,同时你也发现Q只有第一ơ调用该Ҏ(gu)的时候才需要同步,而一旦res初始化完成,同步完全没必要。所以你很快把代码重构成了(jin)下面的样子:(x)
代码3
Class Foo {
Private Resource res = null ;
Public Resource getResource() {
If (res == null ){
synchronized ( this ){
if (res == null ){
res = new Resource();
}
}
}
return res;
}
}
q种看v来很完美的优化技巧就是Double-Checked Locking。但是很遗憾Q根据Java的语a规范Q上面的代码是不可靠的?
造成DCL失效的原因之一是编译器的优化会(x)调整代码的次序。只要是在单个线E情况下执行l果是正的Q就可以认ؓ(f)~译器这L(fng)“自作主张的调整代码ơ序”的行ؓ(f)是合法的。JLS在某些方面的规定比较自由Q就是ؓ(f)?jin)让JVM有更多余地进行代码优化以提高执行效率。而现在的CPU大多使用流水线技术来加快代码执行速度Q针对这L(fng)CPUQ编译器采取的代码优化的Ҏ(gu)之一是在调整某些代码的ơ序Q尽可能保证在程序执行的时候不要让CPU的指令流水线断流Q从而提高程序的执行速度。正是这L(fng)代码调整?x)导致DCL的失效。ؓ(f)?jin)进一步证明这个问题,引用一下《DCL Broken Declaration》文章中的例子:(x)
设一行Java代码Q?
Objects[i].reference = new Object ();
l过Symantec JIT~译器编译过以后Q最l会(x)变成如下汇编码在机器中执行:(x)
可见QObject构造函数尚未调用,但是已经能够通过objects[i].reference获得Object对象实例的引用?
如果把代码放到多U程环境下运行,某线E在执行到该行代码的时候JVM或者操作系l进行了(jin)一ơ线E切换,其他U程昄?x)发现msg对象已经不ؓ(f)I,DLazy load的判断语句if(objects[i].reference == null)不成立。线E认为对象已l徏立成功,随之可能?x)用对象的成员变量或者调用该对象实例的方法,最l导致不可预的错误?
原因之二是在׃n内存的SMPZQ每个CPU有自qCache和寄存器Q共享同一个系l内存。所以CPU可能?x)动态调整指令的执行ơ序Q以更好的进行ƈ行运ƈ且把q算l果与主内存同步。这L(fng)代码ơ序调整也可能导致DCL失效。回想一下前面对Java内存模型的介l,我们q里可以把Main Memory看作pȝ的物理内存,把Thread Working Memory认ؓ(f)是CPU内部的Cache和寄存器Q没有synchronized的保护,Cache和寄存器的内容就不会(x)?qing)时和主内存的内容同步,从而导致一条线E无法看到另一条线E对一些变量的改动?
l合代码3来D例说明,假设Resourcecȝ实现如下Q?
Class Resource{
Object obj;
}
即ResourcecL一个obj成员变量引用?jin)Object的一个实例。假?条线E在q行Q其状态用如下化图表示Q?
?
现在Thread-1构造了(jin)Resource实例Q初始化q程中改动了(jin)obj的一些内宏V退出同步代码段后,因ؓ(f)采取?jin)同步机ӞThread-1所做的改动都会(x)反映C存(sh)。接下来Thread-2获得?jin)新的Resource实例变量resQ由于没有用synchronized保护所以Thread-2不会(x)q行h工作内存的操作。假如之前Thread-2的工作内存(sh)已经有了(jin)obj实例的一份拷贝,那么Thread-2在对obj执行use操作的时候就不会(x)L行load操作,q样一来就无法看到Thread-1对obj的改变,q显然会(x)D错误的运结果。此外,Thread-1在退出同步代码段的时dref和obj执行的写入主存的操作ơ序也是不确定的Q所以即使Thread-2对obj执行?jin)load操作Q也有可能只dobj的初试状态的数据?注:(x)q里的load/use均指JMM定义的操?
有很多h不死?j),试图惛_?jin)很多精妙的办法来解册个问题,但最l都p|?jin)。事实上Q无论是目前的JMMq是已经作ؓ(f)JSR提交的JMM模型的增强,DCL都不能正怋用。在William Pugh的论文《Fixing the Java Memory Model》中详细的探讨了(jin)JMM的一些硬伤,更尝试给Z个新的内存模型,有兴深入研I的读者可以参见文后的参考资料?
如果你设计的对象在程序中只有一个实例,即singleton的,有一U可行的解决办法来实现其LazyLoadQ就是利用类加蝲器的LazyLoadҎ(gu)。代码如下:(x)
Class ResSingleton {
public static Resource res = new Resource();
}
q里ResSingleton只有一个静(rn)态成员变量。当W一ơ用ResSingleton.res的时候,JVM才会(x)初始化一个Resource实例Qƈ且JVM?x)保证初始化的结果?qing)时写入主存,能让其他U程看到Q这样就成功的实C(jin)LazyLoad?
除了(jin)q个办法以外Q还可以使用ThreadLocal来实现DCL的方法,但是׃ThreadLocal的实现效率比较低Q所以这U解军_法会(x)有较大的性能损失Q有兴趣的读者可以参考文后的参考资料?
最后要说明的是Q对于DCL是否有效Q个为更多的是一U带有学I气的推断和讨论。而从U理论的角度来看Q存取Q何可能共享的变量Q对象引用)(j)都需要同步保护,否则都有可能出错Q但是处处用synchronized又会(x)增加死锁的发生几率,苦命的程序员怎么来解册个矛监֑Q事实上Q在很多Java开源项目(比如Ofbiz/Jive{)(j)的代码中都能扑ֈ使用DCL的证据,我在具体的实践中也没有碰到过因DCL而发生的E序异常。个人的偏好是:(x)不妨先大胆用DCLQ等出现问题再用synchronized逐步排除之。也许有人偏于保守,认ؓ(f)E_压倒一切,那就不妨先用synchronized同步hQ我惌是一个见仁见智的问题Q而且得针对具体的目具体分析后才能决定。还有一个办法就是写一个测试案例来试一下系l是否存在DCL现象Q附带的光盘?sh)提供?jin)q样一个例子,感兴的读者可以自行编译测试。不结果怎样Q这L(fng)讨论有助于我们更好的认识JMMQ养成用多线E的思\d析问题的?fn)惯Q提高我们的E序设计能力?
怿你已l了(jin)解了(jin)Java用于同步?板斧Qsynchronized/wait/notifyQ它们的简单而有效。但是在某些情况下,我们需要更加复杂的同步工具。有些简单的同步工具c,诸如ThreadBarrierQSemaphoreQReadWriteLock{,可以自己~程实现。现在要介绍的是牛hDoug Lea的Concurrent包。这个包专门为实现Java高q行E序所开发,可以满我们l大部分的要求。更令h兴奋的是Q这个包公开源代码,可自׃载。且在JDK1.5中该包将作ؓ(f)SDK一部分提供lJava开发h员?
Concurrent Package提供?jin)一pd基本的操作接口,包括syncQchannelQexecutor,barrier,callable{。这里将对前三种接口?qing)其部分zc进行简单的介绍?
sync接口Q?专门负责同步操作Q用于替代Java提供的synchronized关键字,以实现更加灵zȝ代码同步。其cdpd如下Q?
Package org.javaresearch.j2seimproved.thread;
Import EDU.oswego.cs.dl.util.concurrent.*;
public class TestChannel {
final Channel msgQ = new LinkedQueue(); //log信息队列
public static void main( String [] args) {
TestChannel tc = new TestChannel();
For( int i = 0;i < 10;i ++){
Try{
tc.serve();
Thread .sleep(1000);
} catch ( InterruptedException ie){
}
}
}
public void serve() throws InterruptedException {
String status = doService();
//把doService()q回状态放入ChannelQ后台l(f)oggerU程自动d?
msgQ.put(status);
}
private String doService() {
// Do service here
return "service completed OK! " ;
}
public TestChannel() { // start background thread
Runnable logger = new Runnable () {
public void run() {
try {
for (; ; )
System .out.println( "Logger: " + msgQ.take());
}
catch ( InterruptedException ie) {}
}
};
new Thread (logger).start();
}
}
Excutor/ThreadFactory接口: 把相关的U程创徏/回收/l护/调度{工作封装v来,而让调用者只专心(j)于具体Q务的~码工作Q即实现Runnable接口Q,不必昑ּ创徏Threadcd例就能异步执行Q务?
使用Executorq有一个好处,是实现U程的“轻量”用。前面章节曾提到Q即使我们实C(jin)Runnable接口Q要真正的创建线E,q是得通过new Thread()来完成,在这U情况下QRunnable对象(d)和Thread对象(U程)??的关pR如果Q务多而简单,完全可以l每条线E配备一个Q务队列,让Runnable对象(d)和Executor对象变成n:1的关pR用了(jin)ExecutorQ我们可以把上面两种U程{略都封装到具体的Executor实现中,方便代码的实现和l护?
具体的实现有: PooledExecutorQThreadedExecutorQQueuedExecutorQFJTaskRunnerGroup{?
cdpd如下Q?
package org.javaresearch.j2seimproved.thread;
import java.net.*;
import EDU.oswego.cs.dl.util.concurrent.*;
public class TestExecutor {
public static void main( String [] args) {
PooledExecutor pool =
new PooledExecutor( new BoundedBuffer(10), 20);
pool.createThreads(4);
try {
ServerSocket socket = new ServerSocket (9999);
for (; ; ) {
final Socket connection = socket.accept();
pool.execute( new Runnable () {
public void run() {
new Handler ().process(connection);
}
});
}
}
catch ( Exception e) {} // die
}
static class Handler {
void process( Socket s){
}
}
}
限于幅Q这里只是蜻蜓点水式的介l了(jin)Concurrent包,事实上还有相当多有用的接口和cL有提刎ͼ我们的配套光盘(sh)附带?jin)Concurrent包和源代码,感兴的读者可以自行分析?