java中的线程池管理

线程池作用

顾名思义,管理线程的池子,相比于手工创建、运行线程,使用线程池,有如下优点

  • 降低线程创建和销毁线程造成的开销
  • 提高响应速度。任务到达时,相对于手工创建一个线程,直接从线程池中拿线程,速度肯定快很多
  • 提高线程可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统稳定性,使用线程池可以进行统一分配、调优和监控

线程池的创建

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
  • corePoolSize:核心线程最大数量,通俗点来讲就是,线程池中常驻线程的最大数量
  • maximumPoolSize:线程池中运行最大线程数(包括核心线程和非核心线程)
  • keepAliveTime:线程池中空闲线程(仅适用于非核心线程)所能存活的最长时间
  • unit:存活时间单位,与keepAliveTime搭配使用
  • workQueue:存放任务的阻塞队列
  • handler:线程池饱和策略

线程池的执行流程介绍

  1. 判断线程池中核心线程数是否已达阈值corePoolSize,若否,则创建一个新核心线程执行任务
  2. 若核心线程数已达设定值corePoolSize,判断阻塞队列workQueue是否已满,若未满,则将新任务添加进阻塞队列
  3. 若队列已经满,再判断,线程池中线程数是否达到设定值maximumPoolSize,若否,则新建一个非核心线程执行任务。若达到阈值,则执行线程池饱和策略

线程池饱和策略分类

  1. AbortPolicy:直接抛出一个异常,默认策略
  2. DiscardPolicy: 直接丢弃新的任务
  3. DiscardOldestPolicy:抛弃下一个将要被执行的任务(最旧任务),也就是最先加入队列的,再把这个新任务添加进去。在rejectedExecution先从任务队列中弹出最先加入的任务, 空出一个位置,然后再次执行execute方法把任务加入队列,注意新值是加入在队列的最后位置。
  4. CallerRunsPolicy:在主线程中执行任务,策略的缺点就是可能会阻塞主线程

创建线程池例子

最大数量4+队列容量5=9个线程
想要运行的线程数是15个

package com.example.usersport.demothread;


import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Description:
 * @Author: admin
 * @Date: 2021/8/9
 */
public class AbortThread {

    public static void main(String[] args) {
        //直接抛出一个异常,默认策略
        //myRunning(new ThreadPoolExecutor.AbortPolicy());

    }


    public static void myRunning(RejectedExecutionHandler myHandler){
        //核心线程2个,最大线程数为4,工作队列容量为5
        ThreadPoolExecutor exec = new ThreadPoolExecutor(2,4,3, TimeUnit.MILLISECONDS,new LinkedBlockingDeque<>(5));
        if (myHandler != null){
            //设置饱和策略
            exec.setRejectedExecutionHandler(myHandler);
        }
        for (int i = 0; i < 15; i++) {
            //提交任务
            exec.submit(new Task());
        }
        exec.shutdown();

    }

    //自定义任务
    static class Task implements Runnable {
        private static int count = 0;
        private int id = 0;//任务标识
        public Task() {
            id = ++count;
        }
        @Override
        public  void run() {
            try {
                TimeUnit.SECONDS.sleep(3);//休眠3秒
            } catch (InterruptedException e) {
                System.err.println("线程被中断" + e.getMessage());
            }
            System.out.println(" 任务:" + id + "\t 工作线程: "+ Thread.currentThread().getName() + " 执行完毕");
        }
    }

}

执行AbortPolicy策略

    public static void main(String[] args) {
        //直接抛出一个异常,默认策略
        myRunning(new ThreadPoolExecutor.AbortPolicy());
    }

在这里插入图片描述

执行DiscardPolicy策略

    public static void main(String[] args) {
        //直接丢弃新的任务
         myRunning(new ThreadPoolExecutor.DiscardPolicy());
    }

在这里插入图片描述

执行DiscardOldestPolicy策略

    public static void main(String[] args) {
        //会抛弃任务队列中最旧的任务也就是最先加入队列的
         myRunning(new ThreadPoolExecutor.DiscardOldestPolicy());
    }

在这里插入图片描述

执行CallerRunsPolicy策略

    public static void main(String[] args) {
        //主线程中执行任务
         myRunning(new ThreadPoolExecutor.CallerRunsPolicy());
    }

在这里插入图片描述

典型的工作队列分类

  • ArrayBlockingQueue:使用数组实现的有界阻塞队列,特性先进先出,AQS(队列同步器)
  • LinkedBlockingQueue:使用链表实现的阻塞队列,可以设置其容量,默认为Interger.MAX_VALUE,特性先进先出,AQS(队列同步器)
  • PriorityBlockingQueue:使用平衡二叉树堆,实现的具有优先级的无界阻塞队列
  • DelayQueue:无界阻塞延迟队列,队列中每个元素均有过期时间,当从队列获取元素时,只有过期元素才会出队列。队列头元素是最快要过期的元素。没有过期元素的话,使用poll()方法会返回null值,超时判定是通过getDelay(TimeUnit.NANOSECONDS)方法的返回值小于等于0来判断。延时队列不能存放空元素。
  • SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作,必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,CAS(比较交换)

几种典型的线程池

SingleThreadExecutor

使用场景:适用于串行执行任务场景。
创建单个线程。它适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景。
它的corePoolSize和maximumPoolSize被设置为1,使用无界队列LinkedBlockingQueue作为线程池的工作队列

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
  • 当线程池中没有线程时,会创建一个新线程来执行任务。
  • 当前线程池中有一个线程后,将新任务加入LinkedBlockingQueue
  • 线程执行完第一个任务后,会在一个无限循环中反复从LinkedBlockingQueue 获取任务来执行。

FixedThreadPool

使用场景:适用于处理CPU密集型的任务,确保CPU在长期被工作线程使用的情况下,尽可能的少的分配线程,即适用执行长期的任务。

corePoolSize等于maximumPoolSize,所以线程池中只有核心线程,使用无界阻塞队列LinkedBlockingQueue作为工作队列。
FixedThreadPool是一种线程数量固定的线程池,当线程处于空闲状态时,他们并不会被回收,除非线程池被关闭。当所有的线程都处于活动状态时,新的任务都会处于等待状态,直到有线程空闲出来

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>(),
                                  threadFactory);
}
  • 如果当前运行的线程数少于corePoolSize,则创建新线程来执行任务。
  • 在线程数目达到corePoolSize后,将新任务放到LinkedBlockingQueue阻塞队列中。
  • 线程执行完(1)中任务后,会在循环中反复从LinkedBlockingQueue获取任务来执行

CachedThreadPool

使用场景:执行大量短生命周期任务。因为maximumPoolSize是无界的,所以提交任务的速度 > 线程池中线程处理任务的速度就要不断创建新线程;每次提交任务,都会立即有线程去处理,因此CachedThreadPool适用于处理大量、耗时少的任务

核心线程数为0,总线程数量阈值为Integer.MAX_VALUE,即可以创建无限的非核心线程

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
  • 先执行SynchronousQueue的offer方法提交任务,并查询线程池中是否有空闲线程来执行SynchronousQueue的poll方法来移除任务。如果有,则配对成功,将任务交给这个空闲线程
  • 否则,配对失败,创建新的线程去处理任务
  • 当线程池中的线程空闲时,会执行SynchronousQueue的poll方法等待执行SynchronousQueue中新提交的任务。若等待超过60s,空闲线程就会终止

ScheduledThreadPool

使用场景:周期性执行任务,并且需要限制线程数量的场景。

线程总数阈值为Integer.MAX_VALUE,工作队列使用DelayedWorkQueue,非核心线程存活时间为0,所以线程池仅仅包含固定数目的核心线程。

两种方式提交任务:
scheduleAtFixedRate: 按照固定速率周期执行
scheduleWithFixedDelay:上个任务延迟固定时间后执行

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

提问:使用无界队列的线程池会导致内存飙升吗?

答案 :会的,newFixedThreadPool使用了无界的阻塞队列LinkedBlockingQueue,如果线程获取一个任务后,任务的执行时间比较长,会导致队列的任务越积越多,导致机器内存使用不停飙升, 最终导致Out Of Memory。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值