前言
在Java语言中,一切都可以看作是对象,如果要使用对象,那么就new一个出来,线程也是如此。要使用线程,那么肯定是先去创建一个线程,使用完毕后就将线程销毁,这个操作在我们现在的硬件条件下,执行速度是相当快的。但是如果并发线程数量很多的时候,那情况就不一样了,积少成多,会严重地减少相应的速度。那么能不能将使用过的线程先保存下来,在需要线程的时候直接去调用?针对这样的情况,Java中正好有线程池,通过线程池能够在一定程度上减少多个线程运行的时间。
一、ThreadPoolExecutor
并发编程中,JUC是一个关键的包,线程池所涉及的信息也在这个包中。在JUC中,有一个类为ThreadPoolExecutor,这个类是线程池的核心,了解这个类能够帮助我们更好地理解线程池。
ThreadPoolExecutor提供了四个构造方法:
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) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
虽然是四个构造方法,事实上前三个构造方法都是调用的第四个方法进行初始化,而他们的不同就是在于形参上的不同。
线程池中的参数共有七种:corePoolSize 、maximumPoolSize 、keepAliveTime、
TimeUnit、workQueue、ThreadFactory、RejectedExecutionHandler。
在了解这些参数之前,我们不妨先看下线程池的运行流程:
文字表述起来就是:
当线程池执行线程的时候,会先判断当前线程数有没有达到corePoolSize 。如果没有达到,那么就创建一个线程去执行任务,并将当前池中线程数量加1,如果数量超过了corePoolSize,那么去判断任务队列中有没有放满线程。如果没有放满线程,就将任务放到workQueue任务队列中进行等待,当corePoolSize中有空余位置了,workQueue中的任务就去执行。如果放满了线程,那么就去创建临时线程执行任务。但是临时线程的数量不一定是无限制的,它的数量就是maximumPoolSize -corePoolSize ,一旦临时线程的数量超过了这个值,线程池就会执行拒绝策略。
举个例子来看下线程池的流程:
package com.dong.ThreadPool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author 雪浪风尘
* @Remember Keep thinking
*/
public class ThreadPoolProcess {
public static void main(String[] args) {
ThreadPoolExecutor poolExecutor=new ThreadPoolExecutor(5,10,40, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5));
for (int i=0;i<15;i++){
MyTask2 task2=new MyTask2(i);
poolExecutor.execute(task2);
System.out.println("线程池中的数目:"+poolExecutor.getPoolSize()+";消息队列中的任务数目:"+poolExecutor.getQueue().size()+
";已执行完的线程数"+poolExecutor.getCompletedTaskCount());
}
poolExecutor.shutdown();
}
}
class MyTask2 implements Runnable{
private int taskNum;
public MyTask2(int num){
this.taskNum=num;
}
@Override
public void run() {
System.out.println("正在执行第:"+taskNum+"个任务");
try {
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("第"+taskNum+"个任务已经执行完毕");
}
}
运行结果:
正在执行第:0个任务
线程池中的数目:1;消息队列中的任务数目:0;已执行完的线程数0
线程池中的数目:2;消息队列中的任务数目:0;已执行完的线程数0
线程池中的数目:3;消息队列中的任务数目:0;已执行完的线程数0
正在执行第:1个任务
线程池中的数目:4;消息队列中的任务数目:0;已执行完的线程数0
线程池中的数目:5;消息队列中的任务数目:0;已执行完的线程数0
线程池中的数目:5;消息队列中的任务数目:1;已执行完的线程数0
线程池中的数目:5;消息队列中的任务数目:2;已执行完的线程数0
线程池中的数目:5;消息队列中的任务数目:3;已执行完的线程数0
线程池中的数目:5;消息队列中的任务数目:4;已执行完的线程数0
线程池中的数目:5;消息队列中的任务数目:5;已执行完的线程数0
线程池中的数目:6;消息队列中的任务数目:5;已执行完的线程数0
线程池中的数目:7;消息队列中的任务数目:5;已执行完的线程数0
正在执行第:2个任务
线程池中的数目:8;消息队列中的任务数目:5;已执行完的线程数0
线程池中的数目:9;消息队列中的任务数目:5;已执行完的线程数0
正在执行第:3个任务
线程池中的数目:10;消息队列中的任务数目:5;已执行完的线程数0
正在执行第:4个任务
正在执行第:10个任务
正在执行第:11个任务
正在执行第:12个任务
正在执行第:13个任务
正在执行第:14个任务
第0个任务已经执行完毕
正在执行第:5个任务
第1个任务已经执行完毕
正在执行第:6个任务
第2个任务已经执行完毕
正在执行第:7个任务
第3个任务已经执行完毕
第4个任务已经执行完毕
正在执行第:8个任务
正在执行第:9个任务
第12个任务已经执行完毕
第10个任务已经执行完毕
第13个任务已经执行完毕
第11个任务已经执行完毕
第14个任务已经执行完毕
第5个任务已经执行完毕
第6个任务已经执行完毕
第7个任务已经执行完毕
第9个任务已经执行完毕
第8个任务已经执行完毕
当任务进来的时候,核心线程先工作,然后再放到队列。
二、参数介绍
corePoolSize :核心池数量大小。从上面的流程图就可以看出来,他确实是属于核心的,每当有任务过来的时候,都要去判断它有没有能力去执行新的任务,只有它达到了设置的最大值,才会安排别人来处理新的任务。个人理解就是有活你先干,别人先在旁边看,比如这个样子:
在线程池中,默认情况下创建了线程池只是一个空的池子,当有任务的时候才会去创建线程去执行任务,不过可以通过prestartAllCoreThreads()方法创建corePoolSize个线程,或者通过prestartCoreThread()方法一个线程。
maximumPoolSize:线程池中共可以存放的线程数,也就是核心线程数+其它线程数。
keepAliveTime:线程存活时间。当线程执行任务完毕之后,不可能一直存在不销毁,不然的话线程一多,cpu就直接满了。所以通过设置keepAliveTime,可以让线程在执行完任务之后,还能存活一段时间,这段时间没有任务执行,它才会销毁。
TimeUnit:线程存活时间的单位。
TimeUnit提供了七种时间单位:
TimeUnit.DAYS; 天
TimeUnit.HOURS; 小时
TimeUnit.MINUTES; 分
TimeUnit.SECONDS; /秒
TimeUnit.MILLISECONDS; 毫秒
TimeUnit.MICROSECONDS; 微秒
TimeUnit.NANOSECONDS; 纳秒
workQueue:任务队列:用来存放等待中的任务。一般使用到三种。
ArrayBlockingQueue:基于数组的先进先出队列,创建队列的时候必须指定大小。
LinkedBlockingQueue:基于链表的先进先出队列,在创建的时候如果不指定大小,那么默认大小就是Integer.MAX_VALUE
SynchronousQueue:这个队列不会保存提交的任务,而是新建一个线程来执行新来的任务。
ThreadFactory:线程工厂,主要用来创建线程。
RejectedExecutionHandler:拒绝策略:当临时任务也不能创建的时候,就会触发拒绝策略。拒绝策略一般是以下四种:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
除此之外还有largestPoolSize:它是记录这个线程池在过程中的历史最大线程数。
三、运行状态
在学习线程的时候,我们就知道线程有五种状态:创建、就绪、运行、阻塞、销毁,同样的,存放线程的线程池也是有一定的运行状态的。
在JDK中定义了线程池的运行状态:
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
当线程池创建初始化的时候,处于RUNNING状态。
当线程池执行了shutdown方法后,线程池处于SHUTDOWN状态,此时线程池不会接收新的任务,但是会执行剩余未完成的任务。
当线程池执行了shotdownNow()方法后,线程池处于STOP状态,并且尝试去停止未完成的任务。
当线程处于SHUTDOWN或者STOP的时候,并且队列中的任务都被清空,所有任务都执行完毕了,就处于TERMINATED状态。
四、四种线程池
4.1、定长线程池
在之前的线程池流程代码实例中,我们使用了ThreadPoolExecutor创建了一个自定义线程池,但是在Java中有Executors来创建指定的线程池。
newFixedThreadPool:创建指定大小的线程池。表示线程池中最多有指定数量的线程。
package com.dong.ThreadPool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author 雪浪风尘
* @Remember Keep thinking
*/
class fixedThreadPool implements Runnable{
private int num;
public fixedThreadPool(int numTra){
this.num=numTra;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"正在执行");
try {
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"执行完毕");
}
}
public class newFixedThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i=1;i<=10;i++){
fixedThreadPool threadPool=new fixedThreadPool(i);
executorService.execute(threadPool);
}
executorService.shutdown();
}
}
执行结果:
pool-1-thread-1正在执行
pool-1-thread-2正在执行
pool-1-thread-3正在执行
pool-1-thread-1执行完毕
pool-1-thread-1正在执行
pool-1-thread-2执行完毕
pool-1-thread-3执行完毕
pool-1-thread-2正在执行
pool-1-thread-3正在执行
pool-1-thread-1执行完毕
pool-1-thread-1正在执行
pool-1-thread-2执行完毕
pool-1-thread-3执行完毕
pool-1-thread-2正在执行
pool-1-thread-3正在执行
pool-1-thread-1执行完毕
pool-1-thread-1正在执行
pool-1-thread-3执行完毕
pool-1-thread-2执行完毕
pool-1-thread-1执行完毕
可以看到,指定线程池的size为3,运行的结果中线程只有1、2、3三个线程。
4.2、单线程线程池
newSingleThreadExecutor:线程池中只有线程。这种情况下会循环使用线程池中的此线程。
package com.dong.ThreadPool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author 雪浪风尘
* @Remember Keep thinking
*/
class singleThreadPool implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"开始执行");
try {
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"执行完毕");
}
}
public class newSingleThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i=0;i<10;i++){
singleThreadPool single=new singleThreadPool();
executorService.execute(single);
}
executorService.shutdown();
}
}
运行结果:
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
可以看到,运行结果中只有一个线程。
4.3、定时线程池
newScheduledThreadPool:此种线程池可以指定时间去执行线程任务。
package com.dong.ThreadPool;
import org.omg.CORBA.TIMEOUT;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @author 雪浪风尘
* @Remember Keep thinking
*/
class scheduleThreadPool implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"开始执行");
System.out.println(Thread.currentThread().getName()+"执行完毕");
}
}
public class newScheduleThreadPool {
public static void main(String[] args) {
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(5);
//延迟1秒执行
scheduleThreadPool threadPool=new scheduleThreadPool();
scheduledPool.schedule(threadPool,1, TimeUnit.SECONDS);
//延迟1秒后每3秒执行一次
/*scheduleThreadPool threadPool=new scheduleThreadPool();
scheduledPool.scheduleAtFixedRate(threadPool,1,3,TimeUnit.SECONDS);*/
}
}
4.4、可缓存线程池
newCachedThreadPool:创建能够缓存线程的线程池,使得后来的任务能够复用之前的线程。
package com.dong.ThreadPool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author 雪浪风尘
* @Remember Keep thinking
*/
class cacheThreadPool implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"开始执行");
System.out.println(Thread.currentThread().getName()+"执行完毕");
}
}
public class newCacheThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i=0;i<10;i++){
try {
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
cacheThreadPool cachePool=new cacheThreadPool();
executorService.execute(cachePool);
}
executorService.shutdown();
}
}
执行结果:
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
注意
线程池在使用完之后,需要关闭线程池,否则线程池一直运行会极大增大cpu使用。
执行任务:execute()与submit()都可以执行任务,execute()无返回值,而submit()有返回值,但是submit()底层仍然是使用的execute()。
线程池中使用的类都是实现自Executor接口:
public interface Executor {
/**
* Executes the given command at some time in the future. The command
* may execute in a new thread, in a pooled thread, or in the calling
* thread, at the discretion of the {@code Executor} implementation.
*
* @param command the runnable task
* @throws RejectedExecutionException if this task cannot be
* accepted for execution
* @throws NullPointerException if command is null
*/
void execute(Runnable command);
}
这个最原始的接口只有一个方法execute,这个方法中只有一个形参Runnable,所以使用线程池的时候需要涉及到Runnbale。