Contents

频繁地创建和销毁线程会大大降低系统的效率,有一个办法可以使得线程可以复用,即线程完成一个任务,并不会被销毁,而是可以执行其他的任务,这个方法就是线程池。

下面详解Java线程池,从核心的ThreadPoolExecutor讲其实现原理,使用介绍示例,以及如何配置参数。

ThreadPoolExecutor类
我们首先了解一下线程池的好处之后,再去从学习了解它,知其好,然知其内。
线程池的好处主要有三个方面:

  • 重用已经存在的线程,减少线程的创建和销毁的开销。
  • 效控制最大并发的线程数,避免过多的竞争。
  • 提供定时定期等执行方式,很好地控制线程。

我们了解实现线程池的方法有四种:

  • fixedThreadPool() //固定线程数
  • CachedThreadPoll() //按需分配
  • ScheduledThreadPoolExcutor() //定时定期执行任务
  • ThreadPoolExcutor() //指定线程数

实际上,阅读源码可以看出,前三类创建线程池的方法都是返回的ThreadPoolExcutor()的构造实例,只是构造参数不同而已,所以接下来我们只通过对ThreadPoolExcutor()类的解读来了解所有线程池的创建方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ThreadPoolExecutor extends AbstractExecutorService {
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler);
}

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, defaultHandler);
}

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler);
}

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);
}

ThreadPoolExcutor()继承AbstractExcutorService,提供四个构造函数,前三个都是基于第四个修改参数进行的初始化工作。下面就讲讲各个参数的作用:

  • corePoolSize: 核心池的大小
  • maximumPoolSize: 线程池最大线程数
  • keepAliveTime: 非核心线程无任务执行时最长保持时间
  • unit: keepAliveTime的时间单位
  • workQueue: 用来存储待执行任务的阻塞队列
  • threadFactory: 用来创建线程的线程工厂
  • handler: 拒绝处理任务时的策略

具体场景配置可阅读博客

线程的五种状态
1.New (新创建)

当用new操作符创建一个线程时,如new Thread®,该线程还没有开始运行。这意外这它的状态是new。此时程序还没有开始运行线程中的代码,在线程运行之前还有一些基础工作要做。

2.Runnable (可运行/就绪)

一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。

处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序来调度的。

3.Running (运行中)

当线程获得CPU时间片,线程就进入Running状态(如图中的2所示)。
处于Running状态的线程有可能在运行中CPU时间片用完,而run方法没运行完,线程就又进入Runnable状态。
通常情况下,运行中的线程一直处于Running与Runnable交替转换的过程中。
  
4.Blocked (等待/阻塞/睡眠)

当线程在Running状态中,遇到阻塞等待锁、等待用户输入、调用sleep()方法、调用join等待其他线程情况,会导致线程进入阻塞状态(Blocked)。
处于阻塞状态的线程,在阻塞等待结束之后,会进入Runnable状态,等等获得CPU时间片继续运行程序。

线程运行过程中,可能由于各种原因进入阻塞状态:

线程通过调用sleep方法进入睡眠状态;
线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
线程试图得到一个锁,而该锁正被其他线程持有;
线程在等待某个触发条件;
5.Dead (死亡)

有两个原因会导致线程死亡:

run方法正常退出而自然死亡;
一个未捕获的异常终止了run方法而使线程猝死;
为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法,如果是可运行或被阻塞,这个方法返回true;如果线程仍旧是new状态且不是可运行的,或者线程死亡了,则返回false。

线程池的五种状态
1.RUNNING

状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0!

1
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

2.SHUTDOWN

状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。

3.STOP

状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。

4.TIDYING

状态说明:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。
当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。
5.TERMINATED

状态说明:线程池彻底终止,就变成TERMINATED状态。
状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。

Contents