线程池的简单学习与使用

线程池的实现原理

当一个新的任务提交到线程池时

  • 线程池判断核心线程池里的线程是否都在执行任务。

    • 否。则创建一个新的工作线程来执行任务
    • 是。则判断工作队列是否已满
      • 否。则将任务存储在工作队列
      • 是。线程池判断线程池的线程是否都处于工作状态
        • 否。则创建一个新的工作线程来执行任务
        • 是。则交给饱和策略来处理这个任务
  • 流程图如下:
    在这里插入图片描述

ThreadPoolExecutor

几乎所有的线程池框架都是通过ThreadPoolExecutor()来创建线程池的,创建线程池的代码如下:

new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,milliseconds,runnableTaskQueue,handler);

输入参数分析:

  • corePoolSize:线程池的基本大小,即核心线程数,线程池空闲时也要保留在线程池的线程数
  • runnableTaskQueue:任务队列,用于保存等待执行的任务的阻塞队列。常用的阻塞队列如下:
    • ArrayBlockingQueue:一个基于数组结构的有界阻塞队列,按FIFO的规则对元素进行排序
    • LinkedBlockingQueue:一个基于链表结构的阻塞队列,也按照FIFO规则排序元素,吞吐量通常要高于ArrayBlockingQueue。后面会讲到的newFixedThreadPool在创建线程池时使用了该阻塞队列
    • SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作都必须等到另一个线程调用移除操作,否则插入方法将一直被阻塞,吞吐量通常高于LinkedBlockingQueue。后面会讲到的newCachedThreadPool在创建线程池时使用了该阻塞队列
    • PriorityBlockingQueue:一个具有优先级的无限阻塞队列
  • maximumPoolSize:线程池允许创建的最大线程数。
  • ThreadFactory:用于设置创建线程的工厂
  • RejectedExecutionHandler:饱和策略。当队列和线程池都满了,即线程池处于饱和状态时,所采取的处理提交的新任务的策略,默认为AbortPolicy,表示在无法处理新任务时直接抛出异常。线程池框架一共提供了以下策略:
    • AbortPolicy:直接抛出异常
    • CallerRunsPolicy:只用调用者所在线程来运行任务
    • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务
    • DiscardPolicy:不处理,直接丢弃
  • keepAliveTime:线程活动保持时间。即线程池的工作线程空闲后,保持存活的时间
  • TimeUnit:线程活动保持时间的单位。可选项有DAYS(天),HOURS(小时),MINUTES(分钟),MILLISECONDS(毫秒),MICROSECONDS(微秒),NANOSECONDS(纳秒)

常见的线程池框架

FixedThreadPool

FixedThreadPool被称为可重用固定线程数的线程池。源码实现如下:

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

代码示例如下:

public class FixedThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 5; i++) {
            fixedThreadPool.execute(() -> {
                System.out.println("当前线程:" + Thread.currentThread().getName());
            });
        }
    }
}

结果如下:
在这里插入图片描述
可以看出,线程池中不管什么时候最多都只有三个线程在运行

FixedThreadPoolde corePoolSize和maximumPoolSize都被设置为创建newFixedThreadPool时指定的参数nThreads。而keepAliveTime设置为0L,则意味着多余的空闲线程会被立即终止

FixedThreadPool执行execute()方法的流程如下:

  1. 如果当前运行的线程数少于corePoolSize时,则创建新线程来执行任务
  2. 当前运行的线程数等于corePoolSize时,新提交的任务会加入LinkedBlockingQueue
  3. 线程执行完1中的任务后,会在循环中反复从LinkedBlockingQueue中获取任务来执行

FixedThreadPool使用无界队列LinkedBlockingQueue作为线程池的工作队列,队列容量为Integer.MAX_VALUE。使用无界队列作为工作队列会对线程池带来影响:

  1. 线程池中线程数大于corePoolSize时,新任务会在无界队列中等待,故线程池中的线程数不会超过corePoolSize
  2. 因为1,使用无界队列时maximumPoolSize是一个无效参数
  3. 因为1和2,使用无界队列时keepAliveTime是一个无效参数
  4. 由于使用无界队列,运行中的FixedThreadPool不会调用RejectedExecutionHandler.rejectedExecution()方法,即不会拒绝任务

SingleThreadExecutor

SingleThreadExecutor是使用单个worker线程的Executor,适用于需要顺序地执行各个任务,并且任意时刻都只有一个线程在线程池中运行。源码实现如下:

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

SingleThreadExecutor的corePoolSize和maximumPoolSize被设置为1。其他参数与FixedThreadPool相同,同样使用无界队列LinkedBlockingQueue作为线程池的工作队列

代码示例如下:

public class SingleThreadExecutorDemo {
    public static void main(String[] args) {
        ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 5; i++) {
            singleThreadPool.execute(() -> {
                System.out.println("当前线程:" + Thread.currentThread().getName());
            });
        }
    }

}

结果如下:在这里插入图片描述
由上图可知,线程池里始终只有一个线程在执行任务。表面上看它和单线程没有什么区别。
但是当这个唯一的线程因为异常中断或者结束时,则会有一个新的线程来代替它。这点是单线程时做不到的。

CachedThreadPool

CachedThreadPool是一个会根据需要创建新的线程的线程池。

源代码实现如下:

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

分析源码可以看出来,CachedThreadPool的corePooleSize被设置为0,最大线程数设置为Integer.MAX_VALUE,即无界。而工作队列则是使用的没有容量的SynchronousQueue。这就意味着,如果主线程提交任务的速度高于线程池中线程处理任务的速度时,线程池则会不断创建新线程。
在极端情况下,CachedThreadPool会因为创建过多线程而耗尽cpu和内存资源。

代码示例如下:

public class CachedThreadPoolDemo {

    public static void main(String[] args) {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 100; i++) {
            cachedThreadPool.execute(() -> {
                System.out.println("当前线程:" + Thread.currentThread().getName());
            });
        }
    }
}

运行结果如下:
在这里插入图片描述
代码里我们提交了100个任务,但是运行结果最大线程数只有26。那是因为任务提交的速度比线程处理速度慢,线程在处理完任务后,用来处理新的任务(即线程复用),故不需要创建100个线程。

注意

在阿里巴巴java开发规范手册中,不推荐使用Executors工具类创建线程池,因为可能会产生内存溢出问题。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值