pool是什么意思

更新时间:2022-11-25 00:22:03 阅读: 评论:0


2022年11月25日发(作者:arithmetic)

java多线程:线程池原理、阻塞队列

⼀、线程池定义和使⽤

jdk1.5之后就引⼊了线程池。

1.1定义

从上⾯的空间切换看得出来,线程是稀缺资源,它的创建与销毁是⼀个相对偏重且耗资源的操作,⽽Java线程依赖于内核线程,创建线程需要进⾏操作系统状态切换。为避免资源过

度消耗需要设法重⽤线程执⾏多个任务。线程池就是⼀个线程缓存,负责对线程进⾏统⼀分配、调优与监控。(数据库连接池也是⼀样的道理)

什么时候使⽤线程池?

单个任务处理时间⽐较短;需要处理的任务数量很⼤。

线程池优势?

重⽤存在的线程,减少线程创建、消亡的开销,提⾼性能、提⾼响应速度。

当任务到达时,任务可以不需要等到线程创建就能⽴即执⾏。

提⾼线程的可管理性,可统⼀分配,调优和监控。

1.2线程池在jdk已有的实现

在juc包下,有⼀个接⼝:Executor:

Executor⼜有两个⼦接⼝:ExecutorService和ScheduledExecutorService,常⽤的接⼝是ExecutorService。

同时常⽤的线程池的⼯具类叫Executors。

例如:

ExecutorServicervice=hedThreadPool();

Executor框架虽然提供了如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()、newScheduledThreadPool()等创建线程池的⽅法,但都有其局限

性,不够灵活。

上⾯的⼏种⽅式点进去会发现,都是⽤ThreadPoolExecutor进⾏创建的:

newSingleThreadExecutor字⾯意思简单线程执⾏器。

newFixedThreadPool字⾯意思固定的线程池,传参就是线程固定数⽬,适⽤于执⾏长期任务的场景。

newCachedThreadPool字⾯意思缓存线程池,核⼼线程0,最⼤线程⾮常⼤,动态创建的特点。

newScheduledThreadPool字⾯意思时间安排线程池,指定核⼼线程数。

newSingleThreadScheduledExecutor字⾯意思单线程安排执⾏器,也就是基于只有⼀个核⼼线程的执⾏器之外,⼜可以扩展。其中⼜⽤DelegatedExecutorService委托执⾏器服务进

⾏了包装。

可以看到,上⾯直接⽤Executors⼯具类默认的⼀些实现new出来的线程池都是⽤的ThreadPoolExecutor线程执⾏器这个类进⾏构造的,不过参数不同,导致了效果的侧重点不

同。

因此,⾃⼰创建线程池推荐的⽅法就是,直接使⽤ThreadPoolExecutor进⾏个性化的创建:

构造⽅法种的参数有7个:

corePoolSize:线程池维护线程的最少数量(core:核⼼)

maximumPoolSize:线程池维护线程的最⼤数量,显然必须>=1

keepAliveTime:线程池维护的多余的线程所允许的空闲时间,最长可以空闲多久,时间到了,如果超过corePoolSize的线程⼀直空闲,他们就会被销毁。

unit:线程池维护线程所允许的空闲时间的单位

workQueue:线程池所使⽤的缓冲队列,已经提交但是没有执⾏的任务会放进这⾥

threadFactory:⽣成线程池种⼯作线程的线程⼯⼚,⼀般使⽤默认

handler:线程池对拒绝任务的处理策略,当队列满且⼯作线程已经达到maximumPoolSize。

阿⾥的java开发⼿册,强制要求,通过ThreadPoolExecutor来⾃定义,不能使⽤内置的,避免资源耗尽。这个很好理解,1的类型就只有⼀个核⼼线程和最⼤现场,2没有扩展

性,3、4、5的最⼤线程数太⼤,内存会爆炸。

1.3线程池使⽤⽅法

这⾥我们⽤固定线程池来测试,传⼊核⼼线程数为5,最⼤数量⾃然就也是5,

publicstaticvoidmain(String[]args){

ExecutorServicethreadPool=edThreadPool(5);

try{

//模拟10个顾客办理业务

for(inti=0;i<10;i++){

//execute执⾏⽅法,传⼊参数为实现了Runnable接⼝的类

e(()->{

n(tThread().getName()+"号线程办理业务");

});

}

}catch(Exceptione){

tackTrace();

}finally{

wn();

}

}

其中,execute⽅法就是将任务提交的⽅法,我们⽤lambda表达式给execute⽅法传⼊了参数,实际上相当于⼀个完整的实现了Runnable接⼝的类。

执⾏结果:

可以看到,我们循环了10次,执⾏任务,但是线程只⽤到了1-5,其中有多次复⽤。

再⽐如,我们按照各种类型的线程池,⾃⼰定义⼀个线程池,核⼼线程数2,最⼤线程数5,阻塞队列长度为3:

publicstaticvoidmain(String[]args){

ExecutorServicethreadPool=newThreadPoolExecutor(

2,

5,

2L,

S,

newLinkedBlockingDeque<>(3),

tThreadFactory(),

olicy()

);

try{

//模拟10个顾客办理业务

for(inti=0;i<10;i++){

//execute执⾏⽅法,传⼊参数为实现了Runnable接⼝的类

e(()->{

n(tThread().getName()+"号线程办理业务");

});

}

}catch(Exceptione){

tackTrace();

}finally{

wn();

}

}

同样10个线程,执⾏起来:

可以看到,执⾏了8个任务后,就抛出了异常,说明执⾏了拒绝策略。

上⾯两个⽰例,我们的任务本⾝都是没有返回值的,如果创建的任务本⾝需要有返回值就需要实现Callable接⼝,然后搭配FutureTask来传⼊任务,那

么线程池就应该调⽤submit⽅法⽽不是execute。

⼆、线程池底层原理

2.1线程池执⾏逻辑

处理的流程核⼼就execute()⽅法,他接收⼀个实现了Runnable接⼝的任务,决定对这个任务的处理策略。

下图是⼀个⽐较形象的策略流程:

可能的情况有四种,也就是图中的1234:

如果线程池中的线程数量少于corePoolSize,就创建新的核⼼线程来执⾏新添加的任务

如果线程池中的线程数量⼤于等于corePoolSize,但队列workQueue未满,则将新添加的任务放到队列workQueue中

如果线程池中的线程数量⼤于等于corePoolSize,且队列workQueue已满,但线程池中的线程数量⼩于maximumPoolSize,则会创建新的⾮核⼼线程来处理被添加的任务

如果线程池中的线程数量等于了maximumPoolSize,就⽤RejectedExecutionHandler来执⾏拒绝策略。会抛出异常,⼀般的拒绝策略是RejectedExecutionException

注意,执⾏的顺序,在java⾥有⼀个不合理的地⽅:

在池⾥安排任务的时候,我们的核⼼线程,队列,⾮核⼼线程⾥⾯排的任务顺序应该是123;

但是真正实现上,如果三个都满了,开始执⾏的时候,依次执⾏的顺序却是核⼼线程,⾮核⼼线程,队列。也就是执⾏顺序会变成132

2.2拒绝策略

有些时候,我们并不希望拒绝策略是直接抛出异常,那么jdk⾥⾯提供的默认拒绝策略有4种,他们体现在代码中就是ThreadPoolExecutor的四个静态内部类:

2.2.1CallerRunsPolicy:调⽤者运⾏策略。

这种策略不会抛弃任务,也不抛出异常,⽽是将某些任务回退给调⽤者,从⽽降低新任务的流量。

实现⾮常简单,那就是如果说e这个线程池已经shutdown了,那么就什么也不⼲,也就是这个任务直接丢了;否则,(),相当于调⽤这个⽅法的线程⾥直接执⾏了这个

Runnable任务。

此时我们可以把1.3⾥的代码修改⼀下,只修改策略为CallerRunsPolicy:

可以看到,有些任务会在main线程⾥处理。

2.2.2AbortPolicy:终⽌策略。

抛异常。前⾯已经试过了,这个是默认的拒绝策略。

2.2.3DiscardPolicy:丢弃任务。

可以看到,源码⾥就是是什么也不做。如果场景中允许任务丢失,这个是最好的策略。

2.2.4DiscardOldestPolicy:抛弃队列中等待最久的任务。

抛弃队列中等待最久的任务,然后把当前的任务加⼊队列中,尝试再次提交当前任务。

源码⾥也就是利⽤队列操作,进⾏⼀次出队操作,然后重新调⽤execute⽅法。

2.3线程池的五种状态

和⼀个正常的线程的⽣命周期区别开,这个是线程池⾥线程的状态。

Running,能接受新任务以及处理已添加的任务;

Shutdown,不接受新任务,可以处理已经添加的任务,也就是不能再调⽤execute或者submit了;

Stop,不接受新任务,不处理已经添加的任务,并且中断正在处理的任务;

Tidying,所有的任务已经终⽌,CTL记录的任务数量为0,CTL负责记录线程池的运⾏状态与活动线程数量;

Terminated,线程池彻底终⽌,则线程池转变为terminated的状态。

如图所⽰,从running状态转换为shutdown,调⽤shutdown()⽅法;如果调⽤shutdownNow()⽅法,就直接会变成stop。

terminated()是钩⼦函数,默认是什么也不做的,我们可以重写,然后决定结束之前要做⼀些别的处理逻辑。这个钩⼦函数,就是模板模式的⽅法。

三、阻塞队列

线程池⾥的BlockingQueue,阻塞队列,事实上在消费者⽣产者问题⾥的管程法实现,我们的策略也是类似阻塞队列的,⽤它来做⼀个缓存池的作⽤。

阻塞队列:任意时刻,不管并发有多⾼,永远保证只有⼀个线程能够进⾏队列的⼊队或出队操作。也就意味着他是能够保证线程安全的。

另外,阻塞队列分为有界和⽆界队列,理论上来说⼀个是队列的size有固定,另⼀个是⽆界的。对于有界队列来说,如果队列存满,只能出队了,⼊队操作就只能阻塞。

在juc包⾥,阻塞队列的实现有很多:

ArrayBlockingQueue:有界阻塞队列;

LinkedBlockingQueue:链表结构(⼤⼩默认值为_VALUE)的阻塞队列;

PriorityBlockingQueue:⽀持优先级排序的⽆界阻塞队列;

DelayQueue:使⽤优先级队列实现的延迟⽆界阻塞队列;

SynchronousQueue:不存储元素的阻塞队列,相当于只有⼀个元素;

LinkedTransferQueue:链表组成的⽆界阻塞队列;

LinkedBlockingDeque:链表组成的双向阻塞队列。

对于BlockingQueue来说,核⼼操作主要有⼏类:插⼊、删除、查找。

其中的四种异常策略:

抛异常:如果阻塞队列满,再往队列⾥add插⼊元素会抛IllegalStateException:Queuefull,如果阻塞队列空,再remove就会抛NoSuchElementException。

特殊值:offer⽅法:成功true,失败fal,poll⽅法,成功就返回元素,没有就返回null。

阻塞:阻塞队列满的时候,⽣产者线程继续put元素,队列就会阻塞直到可以put数据或者响应中断然后退出,阻塞队列空的时候,消费者线程继续take元素,队列就会⼀直阻塞

直到有元素可以take。

超时退出:阻塞队列满的时候,会阻塞⽣产者线程且超时退出,空的时候会阻塞消费者线程且超时退出。

那么使⽤的时候,增删的⽅法按对应的同⼀组使⽤⽐较合理。(其实这个策略的设计对应的在单线程集合⾥也有,那就是Deque接⼝的实现类LinkedList使⽤的时候,不同的增删⽅

法策略不同)

本文发布于:2022-11-25 00:22:03,感谢您对本站的认可!

本文链接:http://www.wtabcd.cn/fanwen/fan/90/15176.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

上一篇:later什么意思
下一篇:exclusive意思
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图