线程池详解

参考博客:https://blog.youkuaiyun.com/hnd978142833/article/details/80253784

一、线程池简介

  1. 线程池的概念:
    线程池就是提前创建好线程,将创建好的线程集中在一起就是线程池。线程池可以很好的提高系统的性能,线程池在系统启动时,会创建核心空闲线程,程序将一个任务提交给线程池,线程池就会启动一个线程来执行任务,执行完任务后,不会销毁,而是返还线程池中,重新置为空闲状态,等待下一次的任务执行。
  2. 线程池的工作机制:
    2.1.在线程池的编程模式下,任务是提交给线程池,而不是提交给某个线程,线程池在拿到任务后,如果有空闲线程,就在其内部选择一个空闲的线程来执行该任务。
    2.2.一个线程同时只能执行一个任务,但是可以向线程池提交多个任务。
  3. 使用线程池的原因:
    多线程环境下,系统会不断的创建和销毁线程,以及频繁的线程上下文切换,造成资源的浪费,系统性能下降。故线程池是一个好的选择。

二、四种常见线程池详解:

  1. newSingleThreadExecutor():创建一个只有一个线程的线程池,它只会用唯一的工作线程来执行任务,保证所有任务的按照指定(FIFO)顺序执行。

内部实现:

//可见下方线程池参数,再回来观看。
public static ExecutorService newSingleThreadExecutor() {
	//初始创建一个核心线程只有1个,最大线程只有1个的线程池。
	//new LinkedBlockingQueue() 阻塞队列的大小为:Integer.MAX_VALUE
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

测试:

public static void main(String[] args) {
    //创建线程池
    ExecutorService executorService= Executors.newSingleThreadExecutor();
    //将10个任务提交给线程池
    for (int i = 0; i < 10; i++) {
        final int tmp = i;
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "--->" + tmp);
            }
        });
    }
    executorService.shutdown();
}

输出结果:

pool-1-thread-1—>0
pool-1-thread-1—>1
pool-1-thread-1—>2
pool-1-thread-1—>3
pool-1-thread-1—>4
pool-1-thread-1—>5
pool-1-thread-1—>6
pool-1-thread-1—>7
pool-1-thread-1—>8
pool-1-thread-1—>9

  1. newCachedThreadPool():创建一个可缓存的线程池,先查看线程池是否有可用的空闲线程,有则复用该线程,没有则创建一个新的线程加入线程池中,适用于执行许多短期的异步任务。

内部实现:

public static ExecutorService newCachedThreadPool() {
    //初始创建一个线程池,里面没有核心线程,最大线程数量为Integer.MAX_VALUE,相当于无穷大,
    //空闲线程超过60s,就会被销毁。
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>()); //同步队列
}

测试:

public static void main(String[] args) {
    //创建线程池
    ExecutorService executorService= Executors.newCachedThreadPool();
    //将10个任务提交给线程池
    for (int i = 0; i < 10; i++) {
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {}
        
        final int tmp = i;
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "--->" + tmp);
            }
        });
    }
    executorService.shutdown();
}

输出结果:

pool-1-thread-1—>0
pool-1-thread-2—>1
pool-1-thread-2—>2
pool-1-thread-2—>3
pool-1-thread-2—>4
pool-1-thread-1—>6
pool-1-thread-1—>7
pool-1-thread-2—>5
pool-1-thread-2—>8
pool-1-thread-2—>9

  1. newFixedThreadPool():创建一个可重用的固定数量的核心线程和最大线程的线程池,且线程池的线程不会被销毁,直到线程池shutdown。

内部实现:

public static ExecutorService newFixedThreadPool(int nThreads) {
    //指定线程池的核心线程和最大线程数量为 nThreads,且线程创建的线程不会被销毁。
    //new LinkedBlockingQueue() 阻塞队列的大小为:Integer.MAX_VALUE
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

测试:

public static void main(String[] args) {
    //创建线程池
    ExecutorService executorService= Executors.newFixedThreadPool(5);
    //将10个任务提交给线程池
    for (int i = 0; i < 10; i++) {
        final int tmp = i;
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "--->" + tmp);
            }
        });
    }
    executorService.shutdown();
}

输出结果:

pool-1-thread-1—>0
pool-1-thread-1—>5
pool-1-thread-3—>2
pool-1-thread-2—>1
pool-1-thread-3—>7
pool-1-thread-4—>3
pool-1-thread-1—>6
pool-1-thread-5—>4
pool-1-thread-4—>9
pool-1-thread-2—>8

  1. newScheduledThreadPool():创建一个定长线程池,支持定时和周期性的执行任务。

内部实现:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
//创建一个核心线程数为corePoolSize,最大线程数为Integer.MAX_VALUE的线程池
//创建的线程不会被销毁。
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue()); //专门的延迟队列
}

测试:

public static void main(String[] args) {
   //创建线程池
   ExecutorService executorService = Executors.newScheduledThreadPool(5);
   //将10个任务提交给线程池
   for (int i = 0; i < 10; i++) {
       final int tmp = i;
       ((ScheduledExecutorService) executorService).schedule(new Runnable() {
           @Override
           public void run() {
               System.out.println(Thread.currentThread().getName() + "--->" + tmp);
           }
       },1,TimeUnit.SECONDS);  //延时1s,再执行
   }
   executorService.shutdown();
}

输出结果:

//此处会等待1s后再输出
pool-1-thread-1—>0
pool-1-thread-5—>1
pool-1-thread-3—>3
pool-1-thread-4—>2
pool-1-thread-2—>4
pool-1-thread-1—>5
pool-1-thread-3—>8
pool-1-thread-4—>9
pool-1-thread-2—>7
pool-1-thread-5—>6

三、上面四种创建线程池的方式,其底层都是同一个对象,那就是:

new ThreadPoolExecutor(int corePoolSize,	//线程池的核心线程数量
                       int maximumPoolSize,	//线程池的最大线程数量
                       long keepAliveTime,	//线程保持存活的时间
                       TimeUnit unit,		//超时单位
                       BlockingQueue<Runnable> workQueue,	//阻塞队列
                       ThreadFactory threadFactory,			//线程工厂,创建线程的
                       RejectedExecutionHandler handler);	//拒绝策略

1.什么时候触发最大线程数量?

当核心线程都被使用了,而且阻塞队列也满了,这时如果还有任务要处理,就会触发最大线程数量。

2.保持存活时间,是指那些不是核心线程的线程,他们被创建出来使用完后,处于空闲状态,当达到这个存活时间时,这些线程就会被销毁。

3.当我们处理任务达到最大线程池数量,且阻塞队列也满了,还有任务要处理,这些任务就会被拒绝策略处理。

四、拒绝策略:

RejectedExecutionHandler的实现类:

AbortPolicy: 拒绝任务,抛出一个RejectedExecutionException异常。
CallerRunsPolicy: 哪来的回到哪里去 执行。由调用线程来处理该任务。
DiscardPolicy: 不会抛出异常,默认直接丢弃任务。
DiscardOldestPolicy:丢弃最旧的任务,然后重新提交被拒绝的任务。

下面自定义线程池来测试拒绝策略:

public static void main(String[] args) {
    //创建线程池,自定义线程池参数
    ExecutorService executor = new ThreadPoolExecutor(
        2, //核心线程数量
        5, //最大线程数量
        3, //最大存活时间
        TimeUnit.SECONDS, //单位s
        new LinkedBlockingQueue<>(3),  //阻塞队列的大小为3
        Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.DiscardOldestPolicy());

    //测试
    /**
      * i=2: 阻塞队列为空,由两个核心线程处理
      * i=5: 阻塞队列为满,由两个核心线程处理
      * i=6: 阻塞队列为满,会加多一个线程和两个核心线程一起处理
      * i=7:阻塞队列为满,会加多两个线程和两个核心线程一起处理
      * i=8:阻塞队列为满,会加多三个线程和两个核心线程一起处理,此时线程池的线程数量达到线程池的最大上限。
      * i=9:线程的最大线程上限 + 阻塞队列的大小  < 待处理的任务,多余的任务会被拒绝策略拒绝:
      *      new ThreadPoolExecutor.AbortPolicy():使用这个拒绝策略,会直接抛java.util.concurrent.RejectedExecutionException异常
      *      new ThreadPoolExecutor.CallerRunsPolicy():使用这个拒绝策略,会将任务交由调用者来处理,(这里是main线程来处理)。
      *      new ThreadPoolExecutor.DiscardPolicy():使用这个拒绝策略,会将任务直接丢弃,且不会抛出异常。
      *      new ThreadPoolExecutor.DiscardOldestPolicy():使用这个拒绝策略,会将前面的旧任务丢弃,然后重新提交被拒绝的任务。
      */
    for (int i = 0; i < 9; i++) {   //模拟i个任务
        final int tmp = i;
        executor.execute(()->{
            System.out.println(Thread.currentThread().getName()+"=>"+tmp);
        });
    }
    executor.shutdown();    //关闭线程池
}

五、阻塞队列:

https://www.jianshu.com/p/4df275facc15

  1. 缓冲队列BlockingQueue简介:
    BlockingQueue是双缓冲队列。BlockingQueue内部使用两条队列,允许两个线程同时向队列一个存储,一个取出操作。在保证并发安全的同时,提高了队列的存取效率。

  2. 常用的几种BlockingQueue:

  • ArrayBlockingQueue(int i):规定大小的BlockingQueue,其构造必须指定大小。其所含的对象是FIFO顺序排序的。

  • LinkedBlockingQueue()或者(int i):大小不固定的BlockingQueue,若其构造时指定大小,生成的BlockingQueue有大小限制,不指定大小,其大小有Integer.MAX_VALUE来决定。其所含的对象是FIFO顺序排序的。

  • PriorityBlockingQueue()或者(int i):类似于LinkedBlockingQueue,但是其所含对象的排序不是FIFO,而是依据对象的自然顺序或者构造函数的Comparator决定。

  • SynchronizedQueue():特殊的BlockingQueue,对其的操作必须是放和取交替完成。

六、注意事项:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值