Java基础---线程池核心知识总结

本文详细介绍了Java线程池的使用,包括ThreadPoolExecutor的构造及工作原理。探讨了线程池的重要参数如corePoolSize、maximumPoolSize、keepAliveTime等,并讨论了不同类型的线程池及其适用场景,以及为何避免使用Executors创建线程池。同时,文章还提出了如何合理设置线程池大小的建议。
摘要由CSDN通过智能技术生成

一、线程池如何使用

1、架构说明

线程池的底层结构就是ThreadPoolExecutor

2、编码实现

(1)Executors.newScheduledThreadPool():定时执行线程(了解)

(2)Executors.newWorkStealingPool(int):java8新增,可以使用处理器作为并行级别(了解)

(3)Executors.newFixedThreadPool(int):执行长期的任务,性能好很多(重点)

(4)Executors.newSingleThreadExecutor():一个任务一个任务执行的场景(重点)

(5)Executors.newCachedThreadPool():执行很多短期异步的小程序或负载较轻的服务器(重点)

简单的使用:


package com.interview.javabasic.thread;

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

/**
 * Created by luyangsiyi on 2020/3/14
 */
public class MyThreadPoolDemo {
    public static void main(String[] args) {
        //一池5个处理线程
        //ExecutorService threadpool = Executors.newFixedThreadPool(5);
        //一池1个处理线程
        //ExecutorService threadpool = Executors.newSingleThreadExecutor();
        //一池n个处理线程
        ExecutorService threadpool = Executors.newCachedThreadPool();

        //模拟10个用户来办理业务,每个用户就是一个来自外部的请求线程
        try{
            for(int i = 1; i <= 10; i++){
                threadpool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"\t办理业务");
                });
            }
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            threadpool.shutdown();
        }

    }
}

3、ThreadPoolExecutor

重点线程池的底层源码:


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

继续深入到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;
}

二、线程池的底层工作原理

1、线程池的重要参数

(1)corePoolSize

线程池中的常驻核心线程数。

(2)maximumPoolSize

线程池能够容纳同时执行的最大线程数,此值必须大于等于1。

(3)keepAliveTime

多余的空闲线程的存活时间。

当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime时,多余空闲的线程会被销毁直到剩下corePoolSize个线程为止。

(4)unit

keepAliveTime的单位。

默认情况下,只有当前线程池中的线程数大于corePoolSize时,keepAiveTime才会其作用,直到线程池中的线程数不大于corePoolSize。

(5)workQueue(阻塞队列)

任务队列,被提交但尚未被执行的任务。

(6)threadFactory

表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可

(7)handler

拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)

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

② CallerRunsPolicy:调用者运行,既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者。

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

④ DiscardPolicy:直接丢弃任务,不与任何处理也不抛异常。如果允许任务丢失,这是最好的方案。

2、线程池工作原理

(1)在创建了线程池后,等待提交过来的任务请求;
(2)当调用execute()方法添加一个请求任务时,线程池会做如下判断:
① 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
② 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;
③ 如果这个时候队列满了且正在运行的线程数量小于maximumPoolSize,那么继续创建非核心线程立刻运行这个任务;
④ 如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
(3)当一个线程完成任务时,它会从队列中取下一个任务来执行;
(4)当一个线程的空闲时间超过keepAliveTime的时候,线程池会判断:
如果当前运行的线程数大于corePoolSize,那么这个线程会被停掉;
所以线程池的所有任务完成后它最终会收缩到corePoolSize的大小。

3、实际线程池的使用

(1)为什么不能使用Executors去创建线程?

Executors返回的线程池对象的弊端为:
① FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
② CachedThreadPool和ScheduledThreadPool:允许的创建线程数为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

(2)实际使用ThreadPoolExecutor的方式创建

① 自定义线程池的方式

ExecutorService threadPool = new ThreadPoolExecutor(2,
        5,
        1L,
        TimeUnit.SECONDS,
        new LinkedBlockingDeque<Runnable>(3),
        Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.DiscardOldestPolicy());

② 如何合理设置线程池
实际上建议根据实际性能测试后得出。

  • CPU密集型:该任务需要大量的运算,且没有足额色,CPU一直全速运行。
    一般公式:CPU核数+1个线程的线程池。
    核数获得的方法:Runtime.getRuntime().availableProcessors()

  • IO密集型:该任务需要大量的IO,即大量的阻塞。
    参考公式:CPU核数/(1-阻塞系数),阻塞系数位于0.8~0.9之间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值