Executors线程池知识点总结

本文深入探讨了Java线程池的使用,包括线程池的优势、构造参数解析、线程创建策略、线程池大小设定、停止线程池的正确方式以及任务拒绝策略。通过源码分析,阐述了线程池的工作原理,提供了避免线程池问题的注意事项。
摘要由CSDN通过智能技术生成

线程池的自我介绍

思考

如果不使用线程池,我们可能需要每次任务都新开一个线程来处理。

  • 如果只有一个线程
public class oneThread {

    public static void main(String[] args) {
        Thread thread = new Thread(new Task());
        thread.start();
    }

    static class Task implements Runnable{

        @Override
        public void run() {
            System.out.println("正在执行任务");
        }
    }
}

//正在执行任务
  • 如果有十个线程
public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(new Task());
            thread.start();
        }

    }

    static class Task implements Runnable{

        @Override
        public void run() {
            System.out.println("正在执行任务");
        }
    }

/*
    正在执行任务
    正在执行任务
    正在执行任务
    正在执行任务
    正在执行任务
    正在执行任务
    正在执行任务
    正在执行任务
    正在执行任务
    正在执行任务
*/
  • 如果任务数量上升到1000个怎么办?
        for (int i = 0; i < 1000; i++) {
            Thread thread = new Thread(new Task());
            thread.start();
        }

这样的开销太大,会产生反复创建并销毁线程所带来的开销问题。在大量创建线程的时候内存消耗会很大,在线程运行完毕之后需要回收,又会给垃圾回收器带来压力。操作系统创建的线程是有上限的,一直不停创建线程,可能会超过上限导致出错,报出OOM异常。

为什么要使用线程池
  • 返回创建线程开销大
  • 过多的线程会占用太多的内存
创建和停止线程池
线程池构造函数的参数

在这里插入图片描述

corePoolSize

线程池在完成初始化后,默认情况下,线程池中并没有任务线程,线程池会等待任务带来时,在创建新线程去执行任务

maxPoolSize

线程池有可能会在核心线程池的基础上,额外创建一些线程,但是这些线程数会有一个上限,这就是最大量maxPoolSize

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

在这里插入图片描述

keepAliveTime

如果线程池当前的线程多于corePoolSize,那么如果多于的线程空闲时间超过keepAliveTime,他们就会被终止

threadFactory

新的线程是由ThreadFactory创建的,默认使用Executors.defaultThreadFactory(),创建出来的线程都在同一个线程组,拥有相同的优先级并且都不是守护线程。如果自己指定ThreadFactory,那么就可以改变线程名,线程组,优先级,是否是守护线程等

workQueue

有3种常见的队列类型

  • 直接交换

SynchronousQueue

任务不会太多,只是通过队列对任务进行简单的中转,然后交到线程池去执行。

  • 无界队列

LinkedBlockingQueue

最大值为Integer.MAX_VALUE,如果任务的处理速度小于任务提交的速度,会造成队列中任务的堆积,甚至OOM异常

  • 有界队列

ArrayBlockingQueue

需要设置任务的数量

线程需要手动创建还是自动创建

手动创建更好,因为这样可以让我们更加明确线程的运行规律,避免资源耗尽的风险

自动创建可能带来的问题
newFixedThreadPool
public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(4);
        for (int i = 0; i < 1000; i++) {
            service.execute(new Task());
        }
    }

    static class Task implements Runnable{
        @Override
        public void run() {
            try {
                Thread.sleep(500);
                System.out.println(Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

由于传进去的LinkedBlockingQueue是没有容量上限的,所有当请求数越来越多,并且无法及时处理完毕的时候,也就是请求推挤的时候,会容易造成占用大量的内存,可能会导致OOM

OOM异常演示

//-Xmx10m -Xms10m

 private  static ExecutorService service = Executors.newFixedThreadPool(1);
    public static void main(String[] args) {

        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            service.execute(new Task());
        }
    }

class Task implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(500000000);
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

//Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
newSingleThreadExecutor

跟newFixedThreadPool 线程池一样,任务堆积时可能出现OOM异常

private  static ExecutorService service = Executors.newSingleThreadExecutor();
    public static void main(String[] args) {

        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            service.execute(new Task());
        }
    }

class Task implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(500);
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
//pool-1-thread-1
//pool-1-thread-1
//pool-1-thread-1
//... 只有一个线程
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
newCachedThreadPool

可缓存线程池

特点:无界的线程池,具有自动回收多余线程的功能

 private  static ExecutorService service = Executors.newCachedThreadPool();
    public static void main(String[] args) {

        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            service.execute(new Task());
        }
    }

class Task implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(500);
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

//注意控制台打印
//pool-1-thread-423
//...线程数在一直增加
//pool-1-thread-3585
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

这里的弊端在与第二个参数maxPoolSize被设置为了Integer.MAX_VALUE,这可能会创建数量非常多的线程,甚至导致OOM异常

newSingleThreadExecutor
    private static ScheduledExecutorService service = Executors.newScheduledThreadPool(1);

    public static void main(String[] args) {
        //几秒后打印出内容
        service.schedule(new Task(),3, TimeUnit.SECONDS);
        
        //周期性执行任务  第一次是线程启动后3秒执行  以后每隔一秒执行任务
        service.scheduleAtFixedRate(new Task(),3,1,TimeUnit.SECONDS);
    }

  class Task implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
        System.out.println("正在执行任务");
    }
}
正确创建线程的方法
  • 根据不同的任务场景,自己设置线程池的参数,比如我们的内存有多大,我们想给线程去什么名字,任务拒绝后应该怎么记录日志。
线程池里的线程数量设置为多少比较合适
  • CPU密集型(加密等)

    最佳的线程为CPU核心数的1-2倍

  • IO型(读写数据库,文件,网络读写)

    最佳线程数一般会大于cpu数量核心数的很多倍。

  • 通用公式

    线程数 = CPU核心数 * (1+平均等待时间/平均工作时间)

停止线程池的正确方法
shutdown

初始化关闭线程操作,需要等待线程正在执行的任务以及队列中已经存在的任务都执行完毕后,才会关闭线程池

    private static ExecutorService service = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            service.execute(new Task());
        }
        Thread.sleep(1500); //主线程睡眠1500毫秒
        service.shutdown();//shutdown()并不会立马停止线程池
        
        service.execute(new Task());
        //在执行shutdown() 执行之后,继续添加任务到线程池。
        //会报RejectedExecutionException异常

    }

class Task implements Runnable{

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

//运行代码

isShutdown

判断线程池是否处于shutdown()状态。并不代表线程池已经停止

   private static ExecutorService service = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            service.execute(new Task());
        }
        Thread.sleep(1500);
        System.out.println(service.isShutdown()); //false
        service.shutdown();
        System.out.println(service.isShutdown()); //true
    }


class Task implements Runnable{

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

isTerminated

线程池真正停止工作

 private static ExecutorService service = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 100; i++) {
            service.execute(new Task());
        }
        Thread.sleep(100);
        System.out.println(service.isShutdown()); //false
        service.shutdown();
        System.out.println(service.isShutdown()); //true

        System.out.println("----------");
        System.out.println(service.isTerminated()); //false

        Thread.sleep(10000);
        System.out.println(service.isTerminated()); //true
    }

class Task implements Runnable{

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}
awaitTermination

等待一定时间,判断线程时候结束。如果结束返回true, 没有则返回false

  private static ExecutorService service = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 100; i++) {
            service.execute(new Task());
        }

        System.out.println(service.awaitTermination(1,TimeUnit.SECONDS)); //false
        System.out.println(service.awaitTermination(10,TimeUnit.SECONDS)); //true
    }

class Task implements Runnable{

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}
shutdownNow

会中断正在运行的线程,并且返回队列中的线程

    private static ExecutorService service = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 100; i++) {
            service.execute(new Task());
        }

        Thread.sleep(500);
        List<Runnable> list = service.shutdownNow();

        System.out.println(list);
    }


class Task implements Runnable{

    @Override
    public void run() {
        try {
            Thread.sleep(500);
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName()+"中断了");
        }

    }
}

任务的拒绝
拒绝的时机
  • 当Executor关闭时,提交新任务会被拒绝

  • 当Exector对最大线程和工作队列容量使用有限边界并且饱和时

4中拒绝的策略
  • AbortPlicy

    直接拒绝,抛出异常

  • DiscardPolicy

    直接丢弃

  • DiscardOldestPolicy

丢弃最老的任务

  • CallerRunsPolicy

    提交任务的线程来执行

    优点:利用提交的线程的执行任务,会降低线程池接收任务的速度,让线程池有时间来执行其他任务。

线程池的实现原理,源码分析
线程的组成部分
  • 线程池管理器

    来管理线程的创建,关闭操作

  • 工作线程

    创建出来执行任务的线程

  • 任务队列

    存放任务的队列

  • 任务

    要执行的任务

线程池,ThreadPoolExecutor,ExecutorService,Executors,Executor的关系

在这里插入图片描述

Executor

顶层的接口,只有一个方法

public interface Executor {
    void execute(Runnable command);
}
ExecutorService

继承自executor, 扩展了一些管理线程池的方法

Executors

工具类,方便创建线程池。

ThreadPoolExecutor

线程池

线程池的状态
running

接收新任务并处理排队任务

shutdown

不接受新任务,但处理排队任务

stop

不接收新任务,不处理排队的任务,中断正在执行的任务。

tidying

所有的任务都已终止,workcount为0,线程切换到tidying状态,并将运行terminate()

terminate

terminate() 运行完成

线程池的注意点
  • 避免任务的堆积
  • 避免线程数的过度增加
  • 排查线程泄露
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值