java线程池的实现原理

本文来源于《JAVA并发编程的艺术》书

Java中的线程池是运用景最多的并框架,几乎所有需要异步或并发执行任的程序

都可以使用线程池。在开发过程中,合理地使用线程池能够带3个好

第一:降低源消耗。通重复利用已建的线程降低线建和造成的消耗。

第二:提高响速度。当任到达,任可以不需要等到线建就能立即行。

第三:提高线程的可管理性线程是稀缺源,如果无限制地建,不会消耗系统资源,

会降低系定性,使用线程池可以一分配、调优控。但是,要做到合理利用

线程池,必须对实现原理了如指掌。

 

当向线程池提交一个任之后,线程池是如何个任的呢?本来看一下线程池

的主要理流程,理流程9-1所示。

中可以看出,当提交一个新任线程池线程池的理流程如下。

1线程池判断核心线程池里的线程是否都在行任。如果不是,则创建一个新的工作

线程来行任。如果核心线程池里的线程都在行任则进入下个流程。

2线程池判断工作列是否已经满。如果工作列没有将新提交的任

个工作列里。如果工作了,则进入下个流程。

3线程池判断线程池的线程是否都于工作状。如果没有,则创建一个新的工作线

行任。如果已经满了,给饱和策略来个任

ThreadPoolExecutorexecute()方法的示意,如9-2所示。

9-1 线程池的主要理流程

 

9-2 ThreadPoolExecutor行示意

ThreadPoolExecutorexecute方法分下面4种情况。

1)如果当前运行的线程少于corePoolSize则创建新线程来行任(注意,一步

需要取全局)。

2)如果运行的线程等于或多于corePoolSize将任加入BlockingQueue

3)如果无法将任加入BlockingQueue列已),则创建新的线程来理任(注意,一步需要取全局)。

4)如果建新线程将使当前运行的线程超出maximumPoolSize,任将被拒,并

RejectedExecutionHandler.rejectedExecution()方法。

ThreadPoolExecutor采取上述步设计思路,是了在execute()方法,尽可能

地避免取全局(那将会是一个重的可伸)。在ThreadPoolExecutor完成预热之后

(当前运行的线程数大于等于corePoolSize),几乎所有的execute()方法用都是行步2,而

2不需要取全局

分析:上面的流程分析很直地了解了线程池的工作原理,再通源代

来看看是如何实现的,线程池行任的方法如下。

public void execute(Runnable command) {

if (command == null)

throw new NullPointerException();

// 如果线程数小于基本线程数,则创线程并行当前任

if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {

// 线程数大于等于基本线程数或线建失将当前任放到工作列中。

if (runState == RUNNING && workQueue.offer(command)) {

if (runState != RUNNING || poolSize == 0)

ensureQueuedTaskHandled(command);

}

// 如果线程池不于运行中或任无法放入列,并且当前线程数量小于最大允线程数量,

// 则创建一个线行任

else if (!addIfUnderMaximumPoolSize(command))

// 抛出RejectedExecutionException异常

reject(command); // is shutdown or saturated

}

}

工作线程:线程池线,会将线程封装成工作线WorkerWorker行完任

后,会循环获取工作列里的任行。我可以从Workerrun()方法里看到点。

public void run() {

try {

Runnable task = firstTask;

firstTask = null;

while (task != null || (task = getTask()) != null) {

runTask(task);task = null;

}

} finally {

workerDone(this);

}

}

ThreadPoolExecutor线行任的示意9-3所示。

线程池中的线行任分两种情况,如下。

1)在execute()方法中建一个线,会让这线行当前任2线行完上1的任后,会反复从BlockingQueue取任行。

9.2 线程池的使用

9.2.1 线程池的

可以通ThreadPoolExecutor建一个线程池。

new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime,

milliseconds,runnableTaskQueue, handler);

建一个线程池需要入几个参数,如下。

1corePoolSize线程池的基本大小):当提交一个任线程池线程池会建一个线

程来行任,即使其他空的基本线程能够执行新任也会线程,等到需要行的任

数大于线程池基本大小就不再建。如果用了线程池的prestartAllCoreThreads()方法,

线程池会提前建并启所有基本线程。

2runnableTaskQueue(任务队列):用于保存等待行的任的阻塞列。可以选择以下几

个阻塞列。

·ArrayBlockingQueue:是一个基于数组结构的有界阻塞列,此列按FIFO(先先出)原

则对元素行排序。

·LinkedBlockingQueue:一个基于构的阻塞列,此列按FIFO排序元素,吞吐量通

常要高于ArrayBlockingQueue。静工厂方法Executors.newFixedThreadPool()使用了列。

·SynchronousQueue:一个不存元素的阻塞列。每个插入操作必等到另一个线

移除操作,否插入操作一直于阻塞状,吞吐量通常要高于Linked-BlockingQueue,静

厂方法Executors.newCachedThreadPool使用了列。

·PriorityBlockingQueue:一个具有的无限阻塞列。3maximumPoolSize线程池最大数量):线程池允许创建的最大线程数。如果了,并

且已建的线程数小于最大线程数,则线程池会再建新的线行任得注意的是,如

果使用了无界的任务队个参数就没什么效果。

4ThreadFactory:用于线程的工厂,可以通过线程工厂每个建出来的线

置更有意的名字。使用开源框架guava提供的ThreadFactoryBuilder可以快速给线程池里的线

置有意的名字,代如下。

new ThreadFactoryBuilder().setNameFormat("XX-task-%d").build();

5RejectedExecutionHandler和策略):当列和线程池都了,线程池和状

,那么必采取一种策略理提交的新任个策略默情况下是AbortPolicy,表示无法

理新任务时抛出异常。在JDK 1.5Java线程池框架提供了以下4种策略。

·AbortPolicy:直接抛出异常。

·CallerRunsPolicy:只用用者所在线程来运行任

·DiscardOldestPolicy列里最近的一个任,并行当前任

·DiscardPolicy:不理,弃掉。

当然,也可以根据景需要来实现RejectedExecutionHandler接口自定策略。如记录

日志或持久化存不能理的任

·keepAliveTime线程活保持时间):线程池的工作线程空后,保持存活的时间。所以,

如果任很多,并且每个任务执行的时间短,可以时间,提高线程的利用率。

·TimeUnit线程活保持时间位):可位有天(DAYS)、小HOURS)、分

MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和NANOSECONDS,千分之一微秒)。9.2.2 线程池提交任

可以使用两个方法向线程池提交任,分别为execute()submit()方法。

execute()方法用于提交不需要返回的任,所以无法判断任是否被线程池行成功。

以下代可知execute()方法入的任是一个Runnable例。

threadsPool.execute(new Runnable() {

@Override

public void run() {

// TODO Auto-generated method stub

}

});

submit()方法用于提交需要返回的任线程池会返回一个future型的象,通过这

future象可以判断任是否行成功,并且可以通futureget()方法来取返回get()

法会阻塞当前线程直到任完成,而使用getlong timeoutTimeUnit unit)方法会阻塞当前线

程一段时间后立即返回,这时候有可能任没有行完。

Future<Object> future = executor.submit(harReturnValuetask);

try {

Object s = future.get();

} catch (InterruptedException e) {

// 理中断异常

} catch (ExecutionException e) {

// 理无法行任异常

} finally {

// 闭线程池

executor.shutdown();

}9.2.3 闭线程池

可以通过调线程池的shutdownshutdownNow方法来关闭线程池。它的原理是遍历线

程池中的工作线程,然后逐个线程的interrupt方法来中断线程,所以无法响中断的任

可能永无法止。但是它存在一定的区shutdownNow首先将线程池的状态设置成

STOP,然后尝试停止所有的正在行或停任线程,并返回等待行任的列表,而

shutdown只是将线程池的状态设置成SHUTDOWN,然后中断所有没有正在行任线

程。

只要用了两个关方法中的任意一个,isShutdown方法就会返回true。当所有的任

都已关后,才表示线程池关成功,这时调isTerminaed方法会返回true。至于应该调用哪

一种方法来关闭线程池,应该由提交到线程池的任特性决定,通常shutdown方法来关

线程池,如果任不一定要行完,可以shutdownNow方法。9.2.4 合理地配置线程池

要想合理地配置线程池,就必首先分析任特性,可以从以下几个角度来分析。

·的性CPU密集型任IO密集型任和混合型任

·:高、中和低。

·时间、中和短。

·的依性:是否依其他系统资源,如数据库连接。

不同的任可以用不同模的线程池分开理。CPU密集型任务应配置尽可能小的

线程,如配置Ncpu+1线程的线程池。由于IO密集型任务线程并不是一直在行任则应

置尽可能多的线程,如2*Ncpu。混合型的任,如果可以拆分,将其拆分成一个CPU密集型任

和一个IO密集型任,只要两个任务执行的时间相差不是太大,那么分解后行的吞吐量

将高于串行行的吞吐量。如果两个任务执时间相差太大,没必要行分解。可以通

Runtime.getRuntime().availableProcessors()方法得当前设备CPU个数。

不同的任可以使用级队PriorityBlockingQueue理。它可以让优

的任行。

注意 如果一直有高的任提交到列里,那么低的任可能永不能

行。

时间不同的任可以交不同模的线程池来理,或者可以使用级队列,

时间短的任行。

数据库连接池的任,因为线程提交SQL后需要等待数据返回果,等待的时间

CPU闲时间就越,那么线程数应该设置得越大,这样才能更好地利用CPU使用有界。有界列能增加系定性和警能力,可以根据需要大一点

儿,比如几千。有一次,我里后台任务线程池的列和线程池全了,不断抛出抛弃任

的异常,通查发现是数据问题SQL得非常慢,因后台任务线

程池里的任全是需要向数据库查询和插入数据的,所以线程池里的工作线程全部阻

塞,任务积压线程池里。如果当们设置成无界列,那么线程池的列就会越来越多,

有可能会撑内存,致整个系不可用,而不只是后台任现问题。当然,我的系

有的任是用独的服器部署的,我使用不同模的线程池完成不同型的任,但是

现这样问题时也会影响到其他任9.2.5 线程池的

如果在系中大量使用线程池,有必要对线程池控,方便在出现问题时,可以根

线程池的使用状况快速定位问题。可以通过线程池提供的参数控,在线程池的

候可以使用以下属性。

·taskCount线程池需要行的任数量。

·completedTaskCount线程池在运行程中已完成的任数量,小于或等于taskCount

·largestPoolSize线程池里曾经创的最大线程数量。通过这个数据可以知道线程池是

否曾经满过。如等于线程池的最大大小,表示线程池曾经满过

·getPoolSize线程池的线程数量。如果线程池不线程池里的线程不会自动销

,所以个大小只增不减。

·getActiveCount取活线程数。

过扩线程池控。可以通过继线程池来自定义线程池,重写线程池的

beforeExecuteafterExecuteterminated方法,也可以在任务执行前、行后和线程池关

行一些代控。例如,控任的平均时间、最大时间和最小时间等。

几个方法在线程池里是空方法。

protected void beforeExecute(Thread t, Runnable r) { }

9.3 本章小

在工作中我发现,很多人因不了解线程池的实现原理,把线程池配置错误,从而

致了各种问题。本章介什么要使用线程池、如何使用线程池和线程池的使用原理,相信

阅读完本章之后,者能更准确、更有效地使用线程池。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值