12. JUC线程池

*作用

线程池的工作主要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。

*优势

  1. 线程复用

    降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

  2. 控制最大并发数

    提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行。

  3. 管理线程

    提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池则可以进行统一的分配,调优和监控。

*架构

Java中的线程池是通过Executor框架实现的

左侧

Executor:相当于集合类中的Collection接口

ExecutorService:继承了Executor接口的子接口,比Executor更强大,通常我们都用它

AbstractExecutorService:实现了ExecutorService接口的抽象类

ThreadPoolExecutor:重点,底层就是这个类实现的线程池

右侧

ScheduledExecutorService:带时间轮询片调度的,也就是一个定时的线程执行接口

工具类

Executors:使用这个类就可以拿到线程池

*常用的几种线程池

public class MyThreadPoolDemo {
    public static void main(String[] args) {
        // 固定数,core = max,
        // 创建一个线程池,一池有N个固定的线程,有固定线程数的线程,执行长期任务性能好
        ExecutorService es = Executors.newFixedThreadPool(5);

        // 单一数,core/max = 1
        // 一个线程池中只有一个线程,只能一个任务一个任务的执行
        ExecutorService es1 = Executors.newSingleThreadExecutor();

        // 可变数,core = 0,带缓存的线程池
        // 不需要指定容量,线程池根据需要创建新线程,可以自动将新的任务分配给已创建的空闲线程,可扩容,遇强则强
        // 可以创建非常多的线程,这些线程只要一空闲就都会被销毁
        ExecutorService es2 = Executors.newCachedThreadPool();

		// 支持定时任务的线程池
        ExecutorService es3 = Executors.newScheduledThreadPool();
        testThreadPool(es2);
    }

    public static void testThreadPool(ExecutorService es) {
        try {
            for (int i = 1; i <= 10; i++) {
                es.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "\t正在执行任务");
                });
                // 模仿网络延迟,会导致线程分配的不同
                TimeUnit.MILLISECONDS.sleep(1);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            es.shutdown();
        }
    }
}

*七大参数

  1. corePoolSize:常驻核心线程数

  2. maximumPoolSize:最大线程数,必须>=1

    控制资源并发

  3. keepAliveTime:多余线程的存活时间

    如果当前池中线程数量大于核心线程数,并且线程的空闲时间大于指定的存活时间,那就会将线程池的线程数量降到常驻核心线程数

  4. unitkeepAliveTime的时间单位

  5. workQueue:阻塞队列,被提交但未被执行的任务

    如果任务有很多,就会将目前多的任务放在队列里面
    只要有线程空闲,就回去队列里面取出新的任务,继续执行

  6. threadFactory:线程池中生成线程的线程工厂,一般用默认

  7. handler:拒绝策略

    当队列满了,并且工作线程>=线程池的最大线程数(maximumPoolSize)时如何来拒绝请求执行的runnable的策略

*ThreadPoolExecutor底层原理

3个创建线程池的方法实际上调用的都是ThreadPoolExecutor的重载

*线程池底层工作原理

提交任务
核心线程池是否已满
队列是否已满
创建线程执行任务
线程池是否已满
将任务放到队列中
按照策略处理无法执行的任务
创建线程执行任务

  1. 在创建了线程池之后,开始等待请求

  2. 当调用execute()方法添加一个请求任务时,线程池会做出如下判断

    如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务

    如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列

    如果这个时候队列满了且正在运行的线程数量还小于 maximumPoolSize,那么需要创建非核心线程立刻运行这个任务

    如果队列满了且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会启动饱和拒绝策略来执行

  3. 当一个线程完成任务时,它会从队列中取下一个任务来执行

  4. 当一个线程无事可做超过一定的时间( keepAliveTime)时,线程会判断

    如果当前运行的线程数大于 corePoolSize,那么这个线程就会被停掉

    所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小

*实际生产中,使用三种方式的哪一种创建线程

哪种也不用,只用自定义的

*自定义线程池

	ExecutorService es = new ThreadPoolExecutor(
		2,
		Runtime.getRuntime().availableProcessors() + 1,	// 得到CPU内核数
		3L,
		TimeUnit.SECONDS,
		new LinkedBlockingQueue<>(3),
		Executors.defaultThreadFactory(),
		new ThreadPoolExecutor.AbortPolicy()
	);

maximum该怎么设置
首先调用Runtime.getRuntime().availableProcessors()得知服务器是几核的

根据业务类型来决定

CPU密集是指该任务需要大量的运算,没有阻塞,CPU一直全速运行

  • CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程)
  • 所以CPU密集型任务一般配置尽可能少的线程数量
  • 公式:CPU核数+1个线程的线程池

IO密集型任务,即该任务需要大量的IO,会产生大量的阻塞

  • 在单线程上运行IO密集型的任务,会导致大量的CPU运算能力浪费在等待上
  • 公式一:由于IO密集型任务线程并不是一直在执行任务,所以应配置尽可能多的线程,例如:CPU核数*2
  • 公式二:CPU核数/(1-阻塞系数(0.8-0.9之间))

*拒绝策略

AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行

CallerRunsPolicy:“调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。

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

DiscardPolicy:该策略默默地丢弃无法处理的任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种策略。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值