JUC篇——深入理解线程池(深入学习,常见面试题涵盖自定义线程池,常见拒绝策略!!!!!!)

深入理解线程池以及3大方法

一、线程池的优势

1、降低资源消耗
2、提高响应的速度
3、线程放到一个池子中,便于管理
总的来说就是可以实现线程复用、可以控制最大并发数、管理线程


二、线程池三大方法

需要用到Executor工具类,这个工具类有三个创建线程池的方法
1、创建单个线程 Executors.newSingleThreadExecutor()
测试代码:

public class Test01 {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newSingleThreadExecutor();//创建单个线程池

        try {
            for (int i = 1; i <= 10; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName() + "开启了!");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //用完线程池,记得关闭线程池
            threadPool.shutdown();
        }
    }
}

控制台打印结果:
不难发现,自始至终都是一个线程来执行方法

2、创建指定的线程池中线程数量,最高支持指定个线程并发  Executors.newFixedThreadExecutor()
代码实现:

public class Test01 {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(6);

        try {
            for (int i = 1; i <= 10; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName() + "开启了!");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //用完线程池,记得关闭线程池
            threadPool.shutdown();
        }
    }
}

控制台:
最多有6个线程并发执行循环中的方法

3、创建一个可以伸缩大小的线程池  Executors.newCachedFixedThreadExecutor()
代码实现:

public class Test01 {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newCachedThreadPool();//创建一个可伸缩大小的线程池

        try {
            for (int i = 1; i <= 10; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName() + "开启了!");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //用完线程池,记得关闭线程池
            threadPool.shutdown();
        }
    }
}


控制台:
循环中有10次循环,那么就会给我们创建10个线程并发执行

线程池7大参数以及4种拒绝策略

一、首先我们了解到线程池创建线程的方法是通过Executors工具类,有3大方法,分析源码

1、创建单个线程

public static ExecutorService newSingleThreadExecutor() {
    return new Executors.FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()));
}

2、创建指定线程的线程池

public static ExecutorService newFixedThreadPool(int var0) {
    return new ThreadPoolExecutor(var0, var0, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
}

3、创建可伸缩的线程池

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue());
}

分析得知开启线程的本质上就是调用了ThreadPoolExecutor方法

二、分析ThreadPoolExecutor方法(含7大参数的介绍)

public ThreadPoolExecutor(
int var1, //核心线程池大小(一直会开启)
int var2, //最大核心线程池大小(当阻塞队列中等待的任务满了,会开启余下的线程,该参数表示能开启的最多的线程)
long var3, //超时了没有人调用就会释放(当开启了超过核心线程的数量的线程,如果过了超时时间没调用就会释放)
TimeUnit var5, //超时单位
 BlockingQueue<Runnable> var6, //阻塞队列
 ThreadFactory var7, //线程工厂,创建线程的,一般不用动
RejectedExecutionHandler var8 //拒绝策略(当线程都被占用,阻塞队列也满了,那么就会触发拒绝策略,任务要么走,要么等待)) {
    this.ctl = new AtomicInteger(ctlOf(-536870912, 0));
    this.mainLock = new ReentrantLock();
    this.workers = new HashSet();
    this.termination = this.mainLock.newCondition();
    if (var1 >= 0 && var2 > 0 && var2 >= var1 && var3 >= 0L) {
        if (var6 != null && var7 != null && var8 != null) {
            this.acc = System.getSecurityManager() == null ? null : AccessController.getContext();
            this.corePoolSize = var1;
            this.maximumPoolSize = var2;
            this.workQueue = var6;
            this.keepAliveTime = var5.toNanos(var3);
            this.threadFactory = var7;
            this.handler = var8;
        } else {
            throw new NullPointerException();
        }
    } else {
        throw new IllegalArgumentException();
    }
}

三、4种拒绝策略


1、默认的是AbortPolicy策略
超过了最大线程数量+阻塞队列长度的负载量,就会抛出异常

2、CallerRunsPolicy策略:哪里来的去哪里,当线程池触发拒绝策略,如果该任务是main线程中的,那么就由main线程来执行
我设置了超过线程池负载量的任务,超出的任务,由main线程来执行

3、DiscardOldestPolicy策略:当达到最大的负载量,那么超出的任务会和第一个执行的任务去竞争,如果第一个任务快执行完了,那么就会去执行超出来的部分,反之将其抛掉不执行,不会抛出异常
这种情况一般是线程数较大,并且任务量较大的情况下才可能出现和最早的任务竞争的现象,其他情况下一般都是将他抛掉不执行

4、DiscardPolicy策略:当达到了线程池的负载量,不会像AbortPolicy策略那样抛出异常,而是将无法执行的任务丢掉不执行
我设置了最大开启线程数量是5,阻塞队列长度为3,而设置任务数为9,当前状态是超出负载1个任务量
控制台执行结果:依旧是只开启了5个线程,多余不能执行的任务就将其丢掉了

自定义线程池

自定义线程池的前提是了解了ThreadPoolExecutor的底层原理和传递的参数,那么自定义线程池就是根据需求来改变传递的参数即可


自定义线程池的代码

public class Test01 {
    public static void main(String[] args) throws InterruptedException {
        //自定义线程池,工作中只会使用ThreadPoolExecutor,用Executors工具类创建线程池不安全
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
                2, //核心线程池(默认开启的)
                5, //最大线程数
                3, //超时时间
                TimeUnit.SECONDS, //超时单位
                new LinkedBlockingDeque<>(3), //阻塞队列(等待区)
                Executors.defaultThreadFactory(), //默认的线程工厂,不需要改动
                new ThreadPoolExecutor.AbortPolicy() //默认拒绝策略
                );
        //业务代码
        try {
            //计算线程池能够负载的最大的任务数量:最大线程数+阻塞队列(等待区),如果超出了最大负荷量,就会触发拒绝策略,抛出异常
            for (int i = 1; i <= 7; i++) {
                poolExecutor.execute(()->{
                    System.out.println(Thread.currentThread().getName() + "开启了!");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //用完线程池,记得关闭线程池
            poolExecutor.shutdown();
        }
    }
}

CPU密集型和IO密集型(线程调优)

在高并发的状态下,可以存在两种高效率解决实现多线程的方式,换句话说,自定义线程池的时候,线程池的最大线程数有两种设置方式


一、CPU密集型

如果本地CPU核数较高,可以使用CPU密集型(几核CPU就可以并发执行几条线程),可以保证CPU效率最高!
一般我们不会在代码中将CPU核数写死,因为不同的电脑的CPU不同,实现方式如下

//自定义线程池,工作中只会使用ThreadPoolExecutor,用Executors工具类创建线程池不安全
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
        2, //核心线程池(默认开启的)
        Runtime.getRuntime().availableProcessors(), //最大线程数(CPU密集型)
        3, //超时时间
        TimeUnit.SECONDS, //超时单位
        new LinkedBlockingDeque<>(3), //阻塞队
        // 列(等待区)
        Executors.defaultThreadFactory(), //默认的线程工厂,不需要改动
        new ThreadPoolExecutor.DiscardOldestPolicy() //
        );

拓展一下查看本地CPU核数的方法:

二、IO密集型

首先我们需要清楚,IO是非常占用资源的
IO密集型就是判断程序中十分消耗IO的线程,假设我们程序中有15个比较耗IO的线程,我们可以线程为他的一倍,设置30个线程,那么剩下的一半的线程就可以继续执行其他的线程,不会造成系统线程的阻塞等待

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Be explorer

若认可笔者文章,手头富裕望支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值