java 高并发量 线程池_Java高并发,ThreadPoolExecutor线程池技术

Java当中的线程池是通过Executor这个框架接口来实现的,该框架当中用到了Executor,Executors工具类,ExecutorService,ThreadPoolExecutor

107f2009263fc2d73467f8860da168d7.png

Executors创建线程的三种方法:

ExecutorService threadPool = Executors.newFixedThreadPool(5); //固定容量

ExecutorService threadPool = Executors.newSingleThreadExecutor(); //单例的、单个线程的线程池

ExecutorService threadPool = Executors.newCachedThreadPool(); //缓存的 即超出就自动创建线程的

接下来讲解一下这三个的区别:

固定容量的线程池

首先我们看的是第一个固定容量的线程池Executors.newFixedThreadPool(5);:

首先看代码:

/*** 主要特点:线程复用;控制最大并发数;管理线程。

*

*@authorCocowwy

* @create 2020-05-05-20:20

* Executor/ExecutorServic(Interface)

* Executors 线程池的工具类*/

public classMyThreadPoolDemo {public static voidmain(String[] args) {//一池五个受理线程

ExecutorService threadPool = Executors.newFixedThreadPool(5); //看源码是LinkedBlockingQueue()

try{//模拟10个用户办理业务,但是只有5个受理窗口

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

threadPool.execute(()->{

System.out.println(Thread.currentThread().getName()+ "\t" + "办理业务");

});

Thread.sleep(400);

}

}catch(Exception e) {

e.printStackTrace();

}finally{

threadPool.shutdown();//关闭线程池

}

}

}

结果如下:

40f726ee8db54edb87695dd897098ec0.png

接着我们加上一句线程睡眠一小会的代码:

public classMyThreadPoolDemo {public static voidmain(String[] args) {//一池五个受理线程

ExecutorService threadPool = Executors.newFixedThreadPool(5); //看源码是LinkedBlockingQueue()

try{//模拟10个用户办理业务,但是只有5个受理窗口

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

threadPool.execute(()->{

System.out.println(Thread.currentThread().getName()+ "\t" + "办理业务");

});

Thread.sleep(400);

}

}catch(Exception e) {

e.printStackTrace();

}finally{

threadPool.shutdown();//关闭线程池

}

}

}

dad49c1137fbf7814aa7b7125636bfc8.png

在这里我们可以看到有序办理了每个业务。可以看出这个是固定了大小的线程池,每次都是从这个线程池中取的线程。

单例的线程池

这是第二个,单例的线程池:

ExecutorService threadPool = Executors.newSingleThreadExecutor(); //一池1个受理线程

public classMyThreadPoolDemo {public static voidmain(String[] args) {//ExecutorService threadPool = Executors.newFixedThreadPool(5);//一池五个受理线程,看源码是LinkedBlockingQueue()

ExecutorService threadPool = Executors.newSingleThreadExecutor(); //一池1个受理线程

try{

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

threadPool.execute(()->{

System.out.println(Thread.currentThread().getName()+ "\t" + "办理业务");

});

}

}catch(Exception e) {

e.printStackTrace();

}finally{

threadPool.shutdown();//关闭线程池

}

}

}

6f151ac987ce5994712a1a4b3e87691c.png

我们可以看到一直是一个线程在受理业务。

可扩展的线程池

接下来是第三个线程池:ExecutorService threadPool = Executors.newCachedThreadPool(); //一池N个受理线程 可扩展的

ExecutorService threadPool = Executors.newCachedThreadPool(); //一池N个受理线程 可扩展的

接下来上代码:

public classMyThreadPoolDemo {public static voidmain(String[] args) {//ExecutorService threadPool = Executors.newFixedThreadPool(5);//一池五个受理线程,看源码是LinkedBlockingQueue()//ExecutorService threadPool = Executors.newSingleThreadExecutor();//一池1个受理线程

ExecutorService threadPool = Executors.newCachedThreadPool(); //一池N个受理线程

try{

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

threadPool.execute(()->{try{

Thread.sleep(400);

}catch(InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName()+ "\t" + "办理业务");

});

}

}catch(Exception e) {

e.printStackTrace();

}finally{

threadPool.shutdown();//关闭线程池

}

}

}

先看看效果:

b970f578c6728ecde064fabb2cef8b7f.png

在上面的代码中,我们可以发现的是,对从线程池取的线程睡了0.4s,然而却可以发现创建出了3,6,9,10,1,8,7,4,5.....这么多的线程

因为我们睡眠的时间太短了,表明需要受理的业务频率太多,所以才开辟了这么多的线程去处理。

tips:我们可以设置睡眠时间,来控制线程池的开辟数量。当我们将睡眠时间设置的尽可能的大,那么开辟的线程数自然而然的就少了下来,证明需要受理的业务不那么频繁

所以我们可以发现当请求过多,过于频繁的时候使用可扩展的线程池newCachedThreadPool将会创建更多的线程。

线程池的源码

首先点进newFixedThreadPool()的源码可以看到:

dffffc7d1b22914bdba5734d3fd222ec.png

接下来点进去newSingleThreadExecutor()的源码可以看到:

9fa52a3020e12285d16d31e18f629bd6.png

接下来点进去newCachedThreadPool()的源码可以看到:

a0d8d5ab3313c77ceea86f513e8ea873.png

综上所述,返回的实际上只是一个ThreadPoolExecutor(可以看看继承图),利用构造器传入的不同的参数而已,而且我们也能发现底层是阻塞队列。

同时说明我们也可以通过ThreadPoolExecutor`来创建线程池,Executors只是一个创建线程池的工具类,实际上返回的还是ThreadPoolExecutor。

ThreadPoolExecutor的七大参数

接着我们继续点进ThreadPoolExecutor:

951b3d562c795ce1971c44810d88d19b.png

接着再点进这this,我们可以看到它有七个参数,:corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler

1fc0e7b726fe7a1931247eb35373daba.png

下图是这七大参数的解释:

128f677992d9223aefaeff81d947a1d7.png

线程池的底层工作原理图

接下来结合下图理解理解上述的7大参数:

首先看看线程池的底层工作原理图:

79fe4cac80a185e3e5a399744957c6de.png

看上图以及参数解析对照我们可以知道maximumPool包含corePool,maximumPool表示最多能放的线程数,而corePool表示的就是线程的常驻数,可以理解为银行的有最多有5个受理窗口,但是常用的却只有2个。

而候客区就相当于我们的阻塞队列(BlockingQueue),那当我们的阻塞队列满了之后,handle拒绝策略出来了,相当于银行门口立了块牌子,上面写着不办理后面的业务了!

然后当客户都办理的差不多了,此时多出来(在corePool的基础上扩容的窗口)的窗口在经过keepAliveTime的时间后就关闭了,重新恢复到corePool个受理窗口。

总结一下线程池的工作流程:

首先线程池接收到任务,先判断核心线程数是否满了,如果corepool没有满接客则核心(常驻)线程处理。

常驻线程满了就放到阻塞队列,如果阻塞队列没满,这些任务放在阻塞队列。

如果阻塞队列也满了,就扩容线程数到最大线程数。

如果最大线程数也满了,就是我们的拒绝策略。

这就是线程池四大步骤。 接客、放入队列,扩容线程,拒绝策略!

也可以看下图流程解释:太妙了!!

285f1eb5ebc519b77cf7e66ad256046c.png

实际开发当中如何合适的使用线程池

d0d14cd1a03b635dfb8c6df6c1d6c0f5.png

为什么不建议使用Executors工具类去创建线程池?

举个例子,回到之前讲的 newSingleThreadExecutor(); ;以及Executors.newCachedThreadPool( );创建的线程池,看看源码,

正如源码中看到的那样:

8d28328d5dab8d35ef7fff1b06f19020.png

如果用Executors去创建,默认的Integer.MAX_VALUE的大小是21亿............极大的消耗内存,线程池永远不会慢,内存会被你压爆

又如下面源码:

//这是Single的

public staticExecutorService newSingleThreadExecutor() {return newFinalizableDelegatedExecutorService

(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue()));

}

}//点进去LinkedBlockQueue

publicLinkedBlockingQueue() {this(Integer.MAX_VALUE);

}//这是Cahed的

public staticExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue());

}

对于newSingleThreadExecutor()而言,LinkedBlockQueue的长度是Integer.MAX_VALUE,

对于newCachedThreadPool()而言,maximumPool的值竟然为Integer.MAX_VALUE!!

两者均会导致OOM异常!

自定义线程池

public classMyThreadPoolDemo {public static voidmain(String[] args) {//自定义线程池

ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2,5,2L,

TimeUnit.SECONDS,new LinkedBlockingQueue<>(3), //不写的话默认也是Integer.MAX_VALUE

Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());//默认的拒绝策略

try{//模拟10个用户办理业务,但是只有5个受理窗口

for (int i = 0; i < 9; i++) {

threadPool.execute(()->{

System.out.println(Thread.currentThread().getName()+ "\t" + "办理业务");

});

}

}catch(Exception e) {

e.printStackTrace();

}finally{

threadPool.shutdown();//关闭线程池

}

}

threadPool 是我们自定义的线程池,连接过上面的参数的应该都知道。

该线程池最大支持的并发量就应该是maximumPool+Queue的大小,即5+3=8,而超过了大小之后就会报错:java.util.concurrent.RejectedExecutionException 拒绝执行异常

0ebe55448b5b166b6d5283d4f97222ce.png

线程池的四大拒绝策略

接下来我们看看线程池的四大拒绝策略,上述是JDK默认的拒绝策略:

9c47d8f440e706be2dc89e1f854b4ea8.png

接下来看看另外三种策略的运行结果

将上述代码的拒绝策略改成第二种new ThreadPoolExecutor.CallerRunsPolicy(),回退到原始调用者,这里之main线程

703e0593e5ed73c65628f58cddc2681f.png

第三种new ThreadPoolExecutor.DiscardOldestPolicy():不报错。

96fd3130a54de1185e2e0bedbbc2dd7a.png

第四种new ThreadPoolExecutor.DiscardPolicy(): 同样不报错。

e9b4902348597d1c38ec0c55ea199a44.png

以上策略均继承自RejectedExecutionHandler接口。

怎么设置maximumPoolSize合理

最后提一句怎么设置maximumPoolSize合理,

了解:IO密集型,CPU密集型:(调优)

1、CPU 密集型,一般设置为CPU核数加1,可以保持CPu的效率最高!

System.out.println(Runtime.getRuntime().availableProcessors()); //获取CPU的核数,8核

2、IO 密集型, 判断你程序中十分耗IO的线程

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值