JUC并发编程---线程池画图详解

Posted 小样5411

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC并发编程---线程池画图详解相关的知识,希望对你有一定的参考价值。

一、什么是线程池?

说线程池就要先说池化技术:在面向对象编程中,由于创建和销毁对象是十分消耗资源的,所以我们要尽可能减少创建和销毁的次数,池化技术是线程池的核心,就是事先创建若干个可执行的线程放入一个池中,需要的时候就从池中获取线程,使用完毕不用销毁线程而是放回池中,使得线程可以重复利用,从而减少创建和销毁线程对象的开销,并且通过线程池可以管理线程。

线程池需要熟练掌握以下几点:
1、3个方法
2、7种参数
3、4种拒绝策略

二、3个方法

Executors工具类有3大方法来创建线程池,分别是
Executors.newSingleThreadExecutor() //单个线程处理任务
Executors.newFixedThreadPool(参数) //创建一个固定大小的线程池
Executors.newCachedThreadPool() //可伸展, 任务多则线程多, 任务少线程少

注意:最后一个newCachedThreadPool的线程池,底层设置的是Integer.MAX_VALUE,也就是说可以无限大

分别测试一下

package com.yx.pool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class demo1 {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程处理任务
//        ExecutorService threadPool1 = Executors.newFixedThreadPool(5);//创建一个固定大小的线程池
//        ExecutorService threadPool2 = Executors.newCachedThreadPool();//可伸展的,任务多线程多,任务少线程少

        try {
            for (int i = 0; i < 10; i++) {
                //使用线程池创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"->Ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

根据底层源码+阿里巴巴开发手册说明,线程池不允许用Executors创建,因为可能会导致OOM内存溢出问题。要用ThreadPoolExecutor创建,这三个方法了解一下即可。
在这里插入图片描述
在这里插入图片描述

三、7大参数

上面说到有7大参数,我们来看看哪7大,下面是源码,共有7个
在这里插入图片描述
corePoolsize,线程池的基本大小
maximumPoolSize,线程池中允许的最大线程数
keepAliveTime,等待时间(超过这个时间就释放回线程池)
TimeUnit,等待时间的单位,是秒/分/时
BlockingQueue,阻塞队列
Excutors.defaultThreadFactory,创建线程的工厂
defaultHandler,拒绝策略

我们举一个例子让大家明白这7个参数,假设一个银行有5个办理业务的窗口,但平时一般只开两个窗口办业务,如果人多,再开其他窗口

注意:人–任务,业务窗口—线程,等候区–阻塞队列
在这里插入图片描述
如图,办理业务的所有窗口就是最大线程数maximumPoolSize,这里是5个,而只开2个窗口,就表示corePoolsize基本大小为2,就是默认一开始开两个窗口。突然人越来越多,等候区也坐满了,就是阻塞队列BlockingQueue满了,并且还在进人
在这里插入图片描述
那么银行经理看到人太多,排长队了,于是就通知开新窗口
在这里插入图片描述
这时,等候区的人就少了很多,后面可能还有在等候区等候的,但是远远少了很多。慢慢的快到中午了,人数少了很多,不需要这么多窗口了,经理见状等了一等,看着等候区也没啥人,人真的不多了,不需要这么多窗口了,于是就关闭之前开的三个。这个等待时间keepAliveTime,就是等闲下来的时候,设置等待时间,如果超过这个时间,那么就关闭额外开的线程。如keepAliveTime写3,TimeUnit设为秒,就是等待3秒,3秒后释放新开的三个线程,这样就可以做到根据任务量灵活控制。后面讲拒绝策略,先给出下面代码。AbortPolicy是四种其中一种拒绝策略

package com.yx.pool;

import java.util.concurrent.*;

public class demo2 {
    public static void main(String[] args) {
        ExecutorService  threadPool= new ThreadPoolExecutor(
            2,
            5,
            3,
            TimeUnit.SECONDS,
            new LinkedBlockingDeque<>(3),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy()
        );

        try {
            for (int i = 0; i < 10; i++) {
                //使用线程池创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"->Ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}

四、4种拒绝策略

在这里插入图片描述

4.1 AbortPolicy

现在我们来讲这四个策略,四个策略和阻塞队列有关
假设到了下午,人又开始多了起来,等候区又坐了很多人,又需要开窗口了。
在这里插入图片描述

但是虽然全部的窗口都开了,人还是在进,等候区也满了,意思就是最大线程数的5个全部都在占用,并且阻塞队列也全占满了,这时就需要拒绝策略
在这里插入图片描述
我们在代码里看看

new ThreadPoolExecutor.AbortPolicy()//银行满了,还有人进来,不处理这个人,抛出异常

AbortPolicy拒绝策略就是不处理,抛出异常

当执行5个任务时,表示2个任务正在被线程处理,3个在阻塞队列等待,也就是2个在办理业务,3个在等候区等候,这时就用两个线程即可

在这里插入图片描述
代码运行后,可以看到就只有1和2两个线程在处理
在这里插入图片描述
在这里插入图片描述

当执行7个任务时,就会新调用两个线程,执行代码,这时就1,2,3,4这四个线程,最大线程数已经设为5,所以最多只能有5个任务同时被5个线程处理,阻塞队列最多只能有3个等候(代码中设置为3),所以最大承载为8,max+阻塞队列。
在这里插入图片描述
在这里插入图片描述
当有9个任务要处理时,最后一个就会抛出异常,RejectedExecutionException
在这里插入图片描述
总结:所以AbortPolicy就是线程全部被占用,阻塞队列也满了,达到最大承载时,还有任务进来时,就抛出异常

4.2 CallerRunsPolicy

这里拒绝策略就是"哪来的去哪里",就是这个属于哪个线程的就让它去找这个线程,通俗讲就是我们很忙,没时间,你找别人吧
在这里插入图片描述
在这里插入图片描述
第9个由于是main下的,只能回去找main线程了,所以又main处理了

4.3 DiscardPolicy

达到最大承载,不抛出异常,直接丢掉再进来的任务
在这里插入图片描述

4.4 DiscardOldestPolicy

相当于DiscardPolicy升级版,会判断第一个线程是否处理完任务,如果没有,再丢掉任务,也不会抛出异常,也就是说会再努力争取一下
在这里插入图片描述

附加思考:maxinumPoolSize如何确定

1、CPU密集型

由于每个人的电脑能同时并行的线程不一样,这个可以看任务管理器,我这里逻辑处理器是8,就表示能8个线程并行,那么maxinumPoolSize就可以设置为8,但是这就有一个问题,每个人电脑配置不一样,所以我们需要通过代码获取电脑可以并行的线程数
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

2、IO密集型

IO密集型就是通过判断系统中十分耗资源的IO操作,然后定义最大线程>这个数量即可,一般取它的两倍

以上是关于JUC并发编程---线程池画图详解的主要内容,如果未能解决你的问题,请参考以下文章

JUC并发编程 共享模式之工具 线程池 JDK 提供的线程池工具类 -- ThreadPoolExecutor(关闭线程池: shutdownshutdownNow)

JUC并发编程 共享模式之工具 ThreadPoolExecutor -- 正确处理线程池异常

JUC并发编程 共享模式之工具 JUC CountdownLatch(倒计时锁) -- CountdownLatch应用(等待多个线程准备完毕( 可以覆盖上次的打印内)等待多个远程调用结束)(代码片段

JUC并发编程 共享模式之工具 JUC CountdownLatch(倒计时锁) -- CountdownLatch(使用CountdownLatch原理改进: 配合线程池使用)

JUC并发编程 共享模式之工具 线程池 -- 自定义线程池(阻塞队列)

JUC并发编程 共享模式之工具 线程池 JDK 提供的线程池工具类 -- Executors 类创建线程池(创建ThreadPoolExecutor对象)