Java并发编程与技术内幕2 线程池深入理解.docx
- 文档编号:8783908
- 上传时间:2023-02-01
- 格式:DOCX
- 页数:33
- 大小:27.80KB
Java并发编程与技术内幕2 线程池深入理解.docx
《Java并发编程与技术内幕2 线程池深入理解.docx》由会员分享,可在线阅读,更多相关《Java并发编程与技术内幕2 线程池深入理解.docx(33页珍藏版)》请在冰豆网上搜索。
Java并发编程与技术内幕2线程池深入理解
Java并发编程与技术内幕:
线程池深入理解
首先,讲讲什么是线程池?
照笔者的简单理解,其实就是一组线程实时处理休眠状态,等待唤醒执行。
那么为什么要有线程池这个东西呢?
可以从以下几个方面来考虑:
其一、减少在创建和销毁线程上所花的时间以及系统资源的开销。
其二、2将当前任务与主线程隔离,能实现和主线程的异步执行,特别是很多可以分开重复执行的任务。
但是,一味的开线程也不一定能带来性能上的,线池休眠也是要占用一定的内存空间,所以合理的选择线程池的大小也是有一定的依据。
如果你对Java多线程的基本知识还不是很明白的话,推荐你看看博主的另一博文:
Java多线程学习(吐血超详细总结)
一、Executors的API介绍
Java类库提供了许多静态方法来创建一个线程池:
a、newFixedThreadPool创建一个固定长度的线程池,当到达线程最大数量时,线程池的规模将不再变化。
b、newCachedThreadPool创建一个可缓存的线程池,如果当前线程池的规模超出了处理需求,将回收空的线程;当需求增加时,会增加线程数量;线程池规模无限制。
c、newSingleThreadPoolExecutor创建一个单线程的Executor,确保任务对了,串行执行
d、newScheduledThreadPool创建一个固定长度的线程池,而且以延迟或者定时的方式来执行,类似Timer;
小结一下:
在线程池中执行任务比为每个任务分配一个线程优势更多,通过重用现有的线程而不是创建新线程,可以在处理多个请求时分摊线程创建和销毁产生的巨大的开销。
当请求到达时,通常工作线程已经存在,提高了响应性;通过配置线程池的大小,可以创建足够多的线程使CPU达到忙碌状态,还可以防止线程太多耗尽计算机的资源。
创建线程池基本方法:
(1)定义线程类
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
classHandlerimplementsRunnable{
}
(2)建立ExecutorService线程池
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
ExecutorServiceexecutorService=Executors.newCachedThreadPool();
或者
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
intcpuNums=Runtime.getRuntime().availableProcessors();//获取当前系统的CPU数目
ExecutorServiceexecutorService=Executors.newFixedThreadPool(cpuNums*POOL_SIZE);//ExecutorService通常根据系统资源情况灵活定义线程池大小
(3)调用线程池操作
循环操作,成为daemon,把新实例放入Executor池中
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
while(true){
executorService.execute(newHandler(socket));
//classHandlerimplementsRunnable{
或者
executorService.execute(createTask(i));
//privatestaticRunnablecreateTask(finalinttaskID)
}
execute(Runnable对象)方法其实就是对Runnable对象调用start()方法(当然还有一些其他后台动作,比如队列,优先级,IDLEtimeout,active激活等)
二、几种不同的ExecutorService线程池对象
1.newCachedThreadPool()-缓存型池子,先查看池中有没有以前建立的线程,如果有,就reuse.如果没有,就建一个新的线程加入池中
-缓存型池子通常用于执行一些生存期很短的异步型任务
因此在一些面向连接的daemon型SERVER中用得不多。
-能reuse的线程,必须是timeoutIDLE内的池中线程,缺省timeout是60s,超过这个IDLE时长,线程实例将被终止及移出池。
注意,放入CachedThreadPool的线程不必担心其结束,超过TIMEOUT不活动,其会自动被终止。
2.newFixedThreadPool-newFixedThreadPool与cacheThreadPool差不多,也是能reuse就用,但不能随时建新的线程
-其独特之处:
任意时间点,最多只能有固定数目的活动线程存在,此时如果有新的线程要建立,只能放在另外的队列中等待,直到当前的线程中某个线程终止直接被移出池子
-和cacheThreadPool不同,FixedThreadPool没有IDLE机制(可能也有,但既然文档没提,肯定非常长,类似依赖上层的TCP或UDPIDLE机制之类的),所以FixedThreadPool多数针对一些很稳定很固定的正规并发线程,多用于服务器
-从方法的源代码看,cache池和fixed池调用的是同一个底层池,只不过参数不同:
fixed池线程数固定,并且是0秒IDLE(无IDLE)
cache池线程数支持0-Integer.MAX_VALUE(显然完全没考虑主机的资源承受能力),60秒IDLE
3.ScheduledThreadPool-调度型线程池
-这个池子里的线程可以按schedule依次delay执行,或周期执行
4.SingleThreadExecutor-单例线程,任意时间池中只能有一个线程
-用的是和cache池和fixed池相同的底层池,但线程数目是1-1,0秒IDLE(无IDLE)
应用实例:
1.CachedThreadPool
CachedThreadPool首先会按照需要创建足够多的线程来执行任务(Task)。
随着程序执行的过程,有的线程执行完了任务,可以被重新循环使用时,才不再创建新的线程来执行任务。
我们采用《ThinkingInJava》中的例子来分析。
客户端线程和线程池之间会有一个任务队列。
当程序要关闭时,你需要注意两件事情:
入队的这些任务的情况怎么样了以及正在运行的这个任务执行得如何了。
令人惊讶的是很多开发人员并没能正确地或者有意识地去关闭线程池。
正确的方法有两种:
一个是让所有的入队任务都执行完毕(shutdown()),再就是舍弃这些任务(shutdownNow())——这完全取决于你。
比如说如果我们提交了N多任务并且希望等它们都执行完后才返回的话,那么就使用shutdown():
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
importjava.util.Date;
importjava.util.concurrent.ExecutorService;
importjava.util.concurrent.Executors;
importjava.util.concurrent.ScheduledThreadPoolExecutor;
importjava.util.concurrent.TimeUnit;
/**
*功能概要:
缓冲线程池实例-execute运行
*
*@authorlinbingwen
*@since2016年5月24日
*/
classHandleimplementsRunnable{
privateStringname;
publicHandle(Stringname){
this.name="thread"+name;
}
@Override
publicvoidrun(){
System.out.println(name+"Start.Time="+newDate());
processCommand();
System.out.println(name+"End.Time="+newDate());
}
privatevoidprocessCommand(){
try{
Thread.sleep(1000);
}catch(InterruptedExceptione){
e.printStackTrace();
}
}
@Override
publicStringtoString(){
returnthis.name;
}
}
验证实例:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicstaticvoidtestCachedThreadPool(){
System.out.println("Main:
Startingat:
"+newDate());
ExecutorServiceexec=Executors.newCachedThreadPool();//创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE
for(inti=0;i<10;i++){
exec.execute(newHandle(String.valueOf(i)));
}
exec.shutdown();//执行到此处并不会马上关闭线程池,但之后不能再往线程池中加线程,否则会报错
System.out.println("Main:
Finishedallthreadsat"+newDate());
}
执行结果:
从上面的结果可以看出:
1、主线程的执行与线程池里的线程分开,有可能主线程结束了,但是线程池还在运行
2、放入线程池的线程并不一定会按其放入的先后而顺序执行
2.FixedThreadPool
FixedThreadPool模式会使用一个优先固定数目的线程来处理若干数目的任务。
规定数目的线程处理所有任务,一旦有线程处理完了任务就会被用来处理新的任务(如果有的话)。
这种模式与上面的CachedThreadPool是不同的,CachedThreadPool模式下处理一定数量的任务的线程数目是不确定的。
而FixedThreadPool模式下最多的线程数目是一定的。
应用实例:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicstaticvoidtestFixThreadPool(){
System.out.println("MainThread:
Startingat:
"+newDate());
ExecutorServiceexec=Executors.newFixedThreadPool(5);
for(inti=0;i<10;i++){
exec.execute(newHandle(String.valueOf(i)));
}
exec.shutdown();//执行到此处并不会马上关闭线程池
System.out.println("MainThread:
Finishedat:
"+newDate());
}
运行结果:
上面创建了一个固定大小的线程池,大小为5.也就说同一时刻最多只有5个线程能运行。
并且线程执行完成后就从线程池中移出。
它也不能保证放入的线程能按顺序执行。
这要看在等待运行的线程的竞争状态了。
3、newSingleThreadExecutor
其实这个就是创建只能运行一条线程的线程池。
它能保证线程的先后顺序执行,并且能保证一条线程执行完成后才开启另一条新的线程
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicstaticvoidtestSingleThreadPool(){
System.out.println("MainThread:
Startingat:
"+newDate());
ExecutorServiceexec=Executors.newSingleThreadExecutor();//创建大小为1的固定线程池
for(inti=0;i<10;i++){
exec.execute(newHandle(String.valueOf(i)));
}
exec.shutdown();//执行到此处并不会马上关闭线程池
System.out.println("MainThread:
Finishedat:
"+newDate());
}
运行结果:
其实它也等价于以下:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
ExecutorServiceexec=Executors.newFixedThreadPool
(1);
4、newScheduledThreadPool
这是一个计划线程池类,它能设置线程执行的先后间隔及执行时间等,功能比上面的三个强大了一些。
以下实例:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicstaticvoidtestScheduledThreadPool(){
System.out.println("MainThread:
Startingat:
"+newDate());
ScheduledThreadPoolExecutorexec=(ScheduledThreadPoolExecutor)Executors.newScheduledThreadPool(10);//创建大小为10的线程池
for(inti=0;i<10;i++){
exec.schedule(newHandle(String.valueOf(i)),10,TimeUnit.SECONDS);//延迟10秒执行
}
exec.shutdown();//执行到此处并不会马上关闭线程池
while(!
exec.isTerminated()){
//waitforalltaskstofinish
}
System.out.println("MainThread:
Finishedat:
"+newDate());
}
实现每个放入的线程延迟10秒执行。
结果:
ScheduledThreadPoolExecutor的定时方法主要有以下四种:
下面将主要来具体讲讲scheduleAtFixedRate和scheduleWithFixedDelay
scheduleAtFixedRate按指定频率周期执行某个任务
publicScheduledFuture
>scheduleAtFixedRate(Runnablecommand,
longinitialDelay,
longperiod,
TimeUnitunit);
command:
执行线程
initialDelay:
初始化延时
period:
两次开始执行最小间隔时间
unit:
计时单位
scheduleWithFixedDelay周期定时执行某个任务/按指定频率间隔执行某个任务(注意)
publicScheduledFuture
>scheduleWithFixedDelay(Runnablecommand,
longinitialDelay,
longdelay,
TimeUnitunit);
command:
执行线程
initialDelay:
初始化延时
period:
前一次执行结束到下一次执行开始的间隔时间(间隔执行延迟时间)
unit:
计时单位
使用实例:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
classMyHandleimplementsRunnable{
@Override
publicvoidrun(){
System.out.println(System.currentTimeMillis());
try{
Thread.sleep(1*1000);
}catch(InterruptedExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
}
}
1.按指定频率周期执行某个任务
下面实现每隔2秒执行一次,注意,如果上次的线程还没有执行完成,那么会阻塞下一个线程的执行。
即使线程池设置得足够大。
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
/**
*初始化延迟0ms开始执行,每隔2000ms重新执行一次任务
*@authorlinbingwen
*@since2016年6月6日
*/
publicstaticvoidexecuteFixedRate(){
ScheduledExecutorServiceexecutor=Executors.newScheduledThreadPool(10);
executor.scheduleAtFixedRate(
newMyHandle(),
0,
2000,
TimeUnit.MILLISECONDS);
}
间隔指的是连续两次任务开始执行的间隔。
对于scheduleAtFixedRate方法,当执行任务的时间大于我们指定的间隔时间时,它并不会在指定间隔时开辟一个新的线程并发执行这个任务。
而是等待该线程执行完毕。
2、按指定频率间隔执行某个任务
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
/**
*以固定延迟时间进行执行
*本次任务执行完成后,需要延迟设定的延迟时间,才会执行新的任务
*/
publicstaticvoidexecuteFixedDelay(){
ScheduledExecutorServiceexecutor=Executors.newScheduledThreadPool(10);
executor.scheduleWithFixedDelay(
newMyHandle(),
0,
2000,
TimeUnit.MILLISECONDS);
}
间隔指的是连续上次执行完成和下次开始执行之间的间隔。
3.周期定时执行某个任务
周期性的执行一个任务,可以使用下面方法设定每天在固定时间执行一次任务。
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
/**
*每天晚上9点执行一次
*每天定时安排任务进行执行
*/
publicstaticvoidexecuteEightAtNightPerDay(){
ScheduledExecutorServiceexecutor=Executors.newScheduledThreadPool
(1);
longoneDay=24*60*60*1000;
longinitDelay=getTimeMillis("21:
00:
00")-System.currentTimeMillis();
initDelay=initDelay>0?
initDelay:
oneDay+initDelay;
executor.scheduleAtFixedRate(
newMyHandle(),
initDelay,
oneDay,
TimeUnit.MILLISECONDS);
}
/**
*获取指定时间对应的毫秒数
*@paramtime"HH:
mm:
ss"
*@return
*/
privatestaticlonggetTimeMillis(Stringtime){
try{
DateFormatdateFormat=newSimpleDateFormat("yy-MM-ddHH:
mm:
ss");
DateFormatdayFormat=newSimpleDateFormat("yy-MM-dd");
DatecurDate=dateFormat.parse(dayFormat.format(newDate())+""+time);
returncurDate.getTime();
}catch(ParseExceptione){
e.printStackTrace();
}
return0;
}
三、线程池一些常用方法
1、submit()
将线程放入线程池中,除了使用execute,也可以使用submit,它们两个的区别是一个使用有返回值,一个没有返回值。
submit的方法很适应于生产者-消费者模式,通过和Future结合一起使用,可以起到如果线程没有返回结果,就阻塞当前线程等待线程池结果返回。
它主要有三种方法:
一般用第一种比较多
如下实例。
注意,submit中的线程要实现接口Callable
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
packagecom.func.axc.executors;
importjava.util.ArrayList;
importjava.util.List;
importjava.util.concurrent.Callable;
importjava.util.concurrent.ExecutionException;
importjava.util.concurrent.ExecutorService;
importjava.util.concurrent.Executors;
importjava.util.concurrent.Future;
/**
*功能概要:
缓冲线程池实例-submit运行
*
*@authorlinbingwen
*@since2016年5月25日
*/
classTaskWithResultimplementsCallable
privateintid;
publicTaskWithResult(intid){
this.i
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Java并发编程与技术内幕2 线程池深入理解 Java 并发 编程 技术 内幕 线程 深入 理解