线程池【治理线程的最大法宝】

一、线程池的介绍

1.线程池的重要性

(1)如果不使用线程池,那么每一个任务都会新开一个线程
  1. 如果任务很多,那么就会反复创建和销毁很多线程,造成很大的开销
  2. 过多的线程会占用太多内存
(2)线程池的好处
  1. 加快响应速度
  2. 合理利用CPU和内存
  3. 统一管理
(3)线程池适合应用的场合
  1. 服务器:会收到大量请求
  2. 实际开发中,需要创建5个以上的线程时,就可以使用线程池。

二、创建和停止线程池

1.线程池构造函数的参数

在这里插入图片描述

  1. corePoolSize 指的是核心线程数:线程池完成初始化后,默认情况下没有任何线程。等到有任务到来时,线程池才会创建线程去执行任务。核心线程会一直存活
  2. maxPoolSize 线程数的上限。

2.添加线程规则

  1. 如果线程数小于corePoolSize,即使其他工作线程处于空闲状态,也会创建一个新线程来运行新任务。
  2. 线程数大于等于corePoolSize但少于maxPoolSize,则将任务放入队列。
  3. 如果队列已满,并且线程数小于maxPoolSize,则创建一个新线程来运行任务。
  4. 如果队列已满,并且线程数大于等于maxPoolSize,则拒绝该任务。

3.增减线程的特点

  1. 当corePoolSize等于maxPoolSize,就可以创建固定大小的线程池。
  2. 线程池希望保持较少的线程数,只有在负载变得很大时才增加它。
  3. maxPoolSize设置为很高的值,便可以允许线程池容纳任意数量的并发任务。
  4. 如果使用无界队列,那么线程数就不会超过corePoolSize。

4.keepAliveTime

如果线程数多于corePoolSize,那么多余的线程空闲时间超过keepAliveTime就会被终止。

5.workQueue

三种常见队列类型:

  1. 直接交接:SynchronousQueue,没有队列作为缓冲
  2. 无界队列:LinkedBlockingQueue
  3. 有界队列:ArrayBlockingQueue

6.停止线程池的正确方法

  1. shutdown(),线程池会在完成池中的任务后关闭,这期间提交新任务会被拒绝
  2. isShutdown(),可以判断是不是进入停止状态了
  3. isTeminated(),会判断线程池是否完全停止了
  4. awaitTermination(),等待一段时间,如果在此期间线程池停止了就返回true,否则返回false。
  5. shutdownNow(),立刻停止线程池。会给所有正在执行的线程发送interrupt信号,剩下的线程会返回。

三、手动创建还是自动创建

手动创建更好,可以让我们更加明确线程池的运行规则,避免资源耗尽的风险。

自动创建的线程池:

1.FixedThreadPool

FixedThreadPool在创建时指定大小,并且这个线程池永远都是这么大。

使用如下

public class FixedThreadPool {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        for(int i=0;i<1000;i++){
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }
            });
        }
    }
}

通过观察源码,可以发现,其核心线程数与最大线程数相等,并且使用无界队列。这样当请求堆积时,容易造成占用大量的内存,可能会导致OOM。

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

2.SingleThreadExecutor

整个线程池中只有一个线程,原理和FixedThreadPool相同。

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

3.CachedThreadPool

通过观察源码,可以发现其核心线程数为0,最大线程数无穷大,并且使用直接交接。也就是说只要有任务,而且此时没有空闲的线程,就会新建一个线程去执行,当线程空闲60s,便会回收。

创建的线程过多也会导致OOM。

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

4.ScheduledThreadPool

可以用来周期性的执行任务。

    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

采用的是DelayWorkQueue作为工作队列,延迟队列DelayQueue是一个无界阻塞队列,它的队列元素只能在该元素的延迟已经结束或者说过期才能被出队。

5.线程数量设定为多少比较合适

在这里插入图片描述

6.workStealingPool

workStealingPool是JDK1.8加入的。
这个线程池和之前的有很大不同,里面的任务是可以产生子任务的(比如树的遍历)。每个线程之间可以合作,线程有自己的任务队列,当一个线程执行完后就可以去窃取其他线程队列末端的任务去执行。

四、任务太多,怎么拒绝

1.拒绝时机

  1. 当Executor关闭时,提交新任务会被拒绝
  2. 当Executor对最大线程和工作队列容量使用有限边界并且已经饱和时。

2.拒绝策略

  1. AbortPolicy 抛出异常
  2. DiscardPolicy 默默丢弃任务
  3. DiscardOldestPolicy 丢弃队列中最老的任务
  4. CallerRunsPolicy 让提交任务的线程去执行这个任务

五、钩子方法

在任务执行的前后可以添加方法,可以用来做日志、统计以及别的。

这里我们设置一个标志位isPaused,在每次任务执行前都会判断这个标志位,如果被设置为true便暂停线程池。
这样便实现了一个可以暂停的线程池。

//自己写一个可暂停的线程池,每个任务前后都可以写钩子函数
public class PauseThreadPool extends ThreadPoolExecutor {
    private boolean isPaused;
    private final ReentrantLock lock = new ReentrantLock();
    private Condition unpaused = lock.newCondition();

    public PauseThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
        lock.lock();
        try {
            while (isPaused){
                unpaused.await();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    private void pause(){
        lock.lock();
        try {
            isPaused = true;
        }finally {
            lock.unlock();
        }
    }
    private void resume(){
        lock.lock();
        try{
            isPaused = false;
            unpaused.signalAll();
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        PauseThreadPool pauseThreadPool = new PauseThreadPool(10,20,10L,TimeUnit.SECONDS,new LinkedBlockingDeque<>());
        for(int i=0;i<10000;i++){
            pauseThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("我被执行了");
                    try{
                        Thread.sleep(10);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            });
        }
        Thread.sleep(1500);
        pauseThreadPool.pause();
        System.out.println("线程池被暂停了");
        Thread.sleep(1500);
        System.out.println("恢复线程池");
        pauseThreadPool.resume();
    }
}

六、实现原理、源码分析★

1.线程池组成部分

  1. 线程池管理器
  2. 工作线程
  3. 任务队列
  4. 任务接口

2.Executor家族

在这里插入图片描述
Executors是一个工具类,可以直接创建线程池。

3.线程池实现线程复用的原理

   final void runWorker(Worker w) {
   		//wt为当前的线程
        Thread wt = Thread.currentThread();
        //获取传入的任务
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                    	//在其中调用任务的run方法。
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

七、线程池状态

  1. Running:接受新任务并处理排队任务
  2. Shutdown:不接受新任务,但处理排队任务
  3. Stop:不接受新任务,也不处理排队任务,并且中断正在进行的任务。
  4. Tidying:所有任务都已经终止,workerCount为零时,线程会转换到Tidying状态,并将运行terminate()钩子方法。、
  5. Terminated:terminate()运行完成。

八、使用线程池注意点

  1. 避免任务堆积
  2. 避免线程数过度增加
  3. 排查线程泄露
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值