小白的JUC学习06_线程池

线程池


一、线程池介绍

线程池:事先创建一组线程供程序调用,并且可以对其复用

二、为什么需要线程池

  • 降低资源消耗: 通过重复利用已创建的线程降低线程创建和销毁造成的消耗
  • 提高响应速度:当出现新的请求任务时,不需要等待线程创建即可立即使用
  • 提高线程的可管理性 : 可以进行统一的分配,调优和监控

三、使用线程池

  • Java中提供的线程池为:Executors
  • Executors线程池类位于java.util.concurrent包下,实现了Executor接口
  • 我们可以自定义线程池:需要先了解以下知识点

其中我们需要学习三大方法七大参数四大拒绝策略

1.1、Executors的三大方法


newSingleThreadExecutor:创建的线程池例只有一条线程

package com.migu;

import java.util.concurrent.*;

public class Test {

    public static void main(String[] args) {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        try {
            for (int i = 0; i < 5; i++) {
                // 通过execute来获取线程
                executor.execute(()->{
                    System.out.println("当前线程为: " + Thread.currentThread().getName());
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            executor.shutdown(); // 关闭整个线程池(一般也是同服务器共生死,这边只是简单介绍下)
        }
    }

}

打印输出:

newFixedThreadPool可指定线程池中的线程数量

newCachedThreadPool:创建一个自动伸缩的线程池

1.2、ThreadPoolExecutor的七大参数


Executors的三大方法的源码进行分析

  • newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
  • newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
  • newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

我们可以发现,三个创建线程池的方法本质上都是创建了ThreadPoolExecutor对象。

ThreadPoolExecutor的构造方法:需要对七大参数进行设置

public ThreadPoolExecutor(int corePoolSize, // 核心线程池大小
                          int maximumPoolSize, // 最大核心线程池大小
                          long keepAliveTime, // 空闲线程最大存活时间 
                          TimeUnit unit, // 存活时间单位
                          BlockingQueue<Runnable> workQueue, // 阻塞队列(上面提到过)
                          ThreadFactory threadFactory, // 线程工厂
                          RejectedExecutionHandler handler) { // 拒绝策略
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.acc = System.getSecurityManager() == null ?
        null :
    AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

其中在阿里巴巴开发规范中也提出尽量不要使用Executors的三大方法,可以发现在调用newCachedThreadPool方法中,里面最大核心线程池大小被设置为int类型的最大值,达到21亿多,可能会导致堆积大量的线程导致OOM

下图可以粗略的解释上面的情况,来自哔哩哔哩狂神说秦老师

空闲线程指的大概就是:最大线程 减去 核心线程,且当这部分并没有任何任务在执行时,就可认为是空闲线程,此时我们设置的keepAliveTimeunit就发挥了作用,可以对其回收

1.3、四大拒绝策略


从七大参数的最后一个拒绝策略参数handler可知,这是一个RejectedExecutionHandler接口,其中有以下实现类

这些实现类都位于ThreadPoolExecutor的内部类中,为静态内部类

AbortPolicy

  • 这是Executors三大方法的默认实现
  • 会抛出异常

CallerRunsPolicy

  • 阻塞队列满时交由父线程处理该任务

DiscardOldestPolicy

  • 当阻塞队列满时,弹出阻塞队列的头部获取者,并进入该队列【看源码可知】
  • 不会抛出异常

DiscardPolicy

  • 当阻塞队列满时,直接拒绝,也不抛出异常

1.4、自定义线程池


1.4.1、采用AbortPolicy策略


AbortPolicy:该拒绝策略会对最大承载外的线程抛出异常

  • 阻塞队列可自由指定
package com.migu;
import java.util.concurrent.*;

public class Test {
    public static void main(String[] args) {
        // 总共创建五条线程,先开辟两条线程,交由请求任务使用
        // 当阻塞队列满时,开辟所有线程
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            2, // 核心线程池大小
            5, // 总线程数
            10, // 空闲线程存活时间 10
            TimeUnit.SECONDS, // 秒
            new LinkedBlockingDeque<>(3), // 双端阻塞队列,也可以用Array
            Executors.defaultThreadFactory(), // 使用默认的工厂
            new ThreadPoolExecutor.AbortPolicy() // 线程池提供的策略
        );
        try {
            // 当阻塞队列满时,AbortPolicy拒绝策略抛出异常
            for (int i = 0; i < 8; i++) {
                executor.execute(()->{
                    System.out.println("当前线程为: " + Thread.currentThread().getName());
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            executor.shutdown();
        }
    }
}

以上代码i8,也就是存在8个任务,那么总线程数5 + 阻塞队列3正好可以处理

打印输出:

以上代码当i9时,AbortPolicy策略将抛出异常:超出最大承载

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jzBptPqc-1617361044674)(C:\Users\66432\AppData\Roaming\Typora\typora-user-images\1616510726470.png)]
AbortPolicy策略会抛出异常,当该策略抛出异常时,该线程池直接无法使用

分别测试两种实现:

1、线程池可继续使用:

try {
    // 当阻塞队列满时,AbortPolicy拒绝策略抛出异常
    for (int i = 0; i < 15; i++) {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executor.execute(()->{
            System.out.println("当前线程为: " + Thread.currentThread().getName());
        });
    }
} catch (Exception e) {
    e.printStackTrace();
} finally {
    executor.shutdown();
}

控制台打印:
在这里插入图片描述
2、抛出异常时,线程池其余线程再也无法使用:

try {
    // 当阻塞队列满时,AbortPolicy拒绝策略抛出异常
    for (int i = 0; i < 15; i++) {
        executor.execute(()->{
        	// 在线程内部暂停测试 
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("当前线程为: " + Thread.currentThread().getName());
        });
    }
} catch (Exception e) {
    e.printStackTrace();
} finally {
    executor.shutdown();
}

控制台打印:

在这里插入图片描述

1.4.2、采用CallerRunsPolicy策略


这边就直接打印输出:(取i9时)

记住线池的线程时可复用的,请求任务执行完毕会回收回去

1.5、小结


一、最大线程池如何定义

1、CPU密集型

  • 查看当前服务器有几条逻辑处理器,就定义几条线程

  • Runtime.getRuntime().availableProcessors() // 获取当前计算机的线程核数
    

2、IO密集型

  • 判断当前应用程序大概会有几条大型任务的线程
    • 比如是是调用IO任务相关的线程【这玩意就会耗资源】此时存在15条,那么就定义30条线程
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值