合理利用线程池能够带来三个好处。
Executor线程池框架的最大优点是把任务的提交和执行解耦。客户端将要执行的任务封装成Task,然后提交即可。而Task如何执行客户端则是透明的。具体点讲,提交一个Callable对象给ExecutorService(如最常用的线程池ThreadPoolExecutor),将得到一个Future对象,调用Future对象的get方法等待执行结果。
下图是线程池所涉及到的所有类的结构图(右键查看大图),先从整体把握下:
图1 线程池实现原理类结构图
上面这个图是很复杂的,涉及到了线程池内部实现原理的所有类,不利于我们理解线程池如何使用。我们先从客户端的角度出发,看看客户端使用线程池所涉及到的类结构图:
图2 线程池使用的基本类结构图
从图一可知,实际的线程池类是实现ExecutorService接口的类,有ThreadPoolExecutor、ForkJoinPool和ScheduledThreadPoolExecutor。下面以常用的ThreadPoolExecutor为例讲解。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数说明:
allowCoreThreadTimeOut
is set.当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads
方法,线程池会提前创建并启动所有基本线程。 由此可见,创建一个线程所需的参数很多,线程池为我们提供了类Executors的静态工厂方法以创建不同类型的线程池。
- newFixedThreadPool
可以生成固定大小的线程池;
- newCachedThreadPool
可以生成一个无界、可以自动回收的线程池;
- newSingleThreadScheduledExecutor
可以生成一个单个线程的线程池;
- newScheduledThreadPool
还可以生成支持周期任务的线程池。
有两种方式提交任务:
1.使用void execute(Runnable command)
方法提交任务
execute方法返回类型为void,所以没有办法判断任务是否被线程池执行成功。
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("Task is running by " + Thread.currentThread().getName());
System.out.println("线程池正在执行的线程数:" + threadPoolExecutor.getActiveCount());
}
};
threadPool.execute(task);
2.使用submit
方法提交任务
Future> submit(Runnable task);
、
和 Future
会返回一个Future,可以通过这个future来判断任务是否执行成功,通过future的get方法来获取返回值,get方法会阻塞直到任务完成,而使用get(long timeout, TimeUnit unit)方法则会阻塞一段时间后立即返回,这时有可能任务没有执行完。
Future> future = threadPool.submit(task);
try {
Object result = future.get();
System.out.println("任务是否完成:" + future.isDone());
System.out.println("返回的结果为:" + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
// 关闭线程池
threadPool.shutdown();
}
shutdown()
方法:这个方法会平滑地关闭ExecutorService,当我们调用这个方法时,ExecutorService停止接受任何新的任务且等待已经提交的任务执行完成(已经提交的任务会分两类:一类是已经在执行的,另一类是还没有开始执行的),当所有已经提交的任务执行完毕后将会关闭ExecutorService。awaitTermination(long timeout, TimeUnit unit)
方法:这个方法有两个参数,一个是timeout即超时时间,另一个是unit即时间单位。这个方法会使当前关闭线程池的线程等待timeout时长,当超过timeout时间后,则去监测ExecutorService是否已经关闭,若关闭则返回true,否则返回false。一般情况下会和shutdown方法组合使用。shutdownNow()
方法:这个方法会强制关闭ExecutorService,它将取消所有运行中的任务和在工作队列中等待的任务,这个方法返回一个List列表,列表中返回的是等待在工作队列中的任务。// 4. 关闭线程池
threadPool.shutdown();
// hreadPool.shutdownNow();
System.out.println("线程池是否关闭:" + threadPool.isShutdown());
try {
//当前线程阻塞10ms后,去检测线程池是否终止,终止则返回true
while(!threadPool.awaitTermination(10, TimeUnit.MILLISECONDS)) {
System.out.println("检测线程池是否终止:" + threadPool.isTerminated());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程池是否终止:" + threadPool.isTerminated());
完整案例:
package com.markliu.concurrent.threadpool;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolDemo1 {
public static void main(String[] args) {
/*
* 1. 创建线程池
*
* 创建一个固定线程数目的线程池。corePoolSize = maximumPoolSize = 5
* 即线程池的基本线程数和最大线程数相等。
* 相当于:
* new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue());
*/
ExecutorService threadPool = Executors.newFixedThreadPool(5);
/*
* 2. 封装任务并提交给线程池
*/
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) threadPool;
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("Task is running by " + Thread.currentThread().getName());
System.out.println("线程池正在执行的线程数:" + threadPoolExecutor.getActiveCount());
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
/*
* Starts all core threads, causing them to idly wait for work.
* This overrides the default policy of starting core threads
* only when new tasks are executed.
*/
int count = threadPoolExecutor.prestartAllCoreThreads();
System.out.println("开启的所有core线程数:" + count);
System.out.println("线程池当前线程数:" + threadPoolExecutor.getPoolSize());
System.out.println("线程池的core number of threads:" + threadPoolExecutor.getCorePoolSize());
System.out.println("线程池中的最大线程数:" + threadPoolExecutor.getLargestPoolSize());
// 3. 执行,获取返回结果
/**
* execute方式提交任务
*/
// threadPool.execute(task);
/**
* submit方式提交任务
*/
Future> future = threadPool.submit(task);
try {
// 阻塞,等待线程执行完成,并获得结果
Object result = future.get();
System.out.println("任务是否完成:" + future.isDone());
System.out.println("返回的结果为:" + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
System.out.println("线程池中已经执行完的任务数:" + threadPoolExecutor.getCompletedTaskCount());
// 4. 关闭线程池
/*
* shutdown方法平滑地关闭线程池,将线程池的状态设为:SHUTDOWN
* 停止接受任何新的任务且等待已经提交的任务执行完成,当所有已经
* 提交的任务执行完毕后将会关闭线程池
*/
threadPool.shutdown();
/*
* shutdownNow方法强制关闭线程池,将线程池状态设置为:STOP
* 取消所有运行中的任务和在工作队列中等待的任务,并返回所有未执行的任务List
*/
// hreadPool.shutdownNow();
System.out.println("线程池是否关闭:" + threadPool.isShutdown());
try {
//当前线程阻塞10ms后,去检测线程池是否终止,终止则返回true
while(!threadPool.awaitTermination(10, TimeUnit.MILLISECONDS)) {
System.out.println("检测线程池是否终止:" + threadPool.isTerminated());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程池是否终止:" + threadPool.isTerminated());
}
}
}
线程池的主要工作流程如下图:
当提交一个新任务到线程池时,线程池的处理流程如下:
- 首先线程池判断“基本线程池”(corePoolSize)是否已满?没满,创建一个工作线程来执行任务。满了,则进入下个流程。
- 其次线程池判断工作队列(workQueue)是否已满?没满,则将新提交的任务存储在工作队列里。满了,则进入下个流程。
- 最后线程池判断整个线程池的线程数是否已超过maximumPoolSize?没满,则创建一个新的工作线程来执行任务,满了,则交给拒绝策略来处理这个任务。
(我的理解:提交任务—>如果线程数未达到corePoolSize,则创建线程执行任务—>如果达到corePoolSize,仍让提交了任务,则会有任务等待,所以将任务保存在任务队列中,直到任务队列workQueue已满—>如果workQueue已满,仍然有任务提交,但未达到最大线程数,则继续创建线程执行任务,直到线程数达到maximumPoolSize,如果达到了maximumPoolSize,则根据饱和策略拒绝该任务。这也就解释了为什么有了corePoolSize还有maximumPoolSize的原因。)
关于线程池的工作原理后期从源代码分析。
通过线程池提供的参数进行监控:
- taskCount:线程池需要执行的任务数量。
- completedTaskCount:线程池在运行过程中已完成的任务数量。小于或等于taskCount。
- largestPoolSize:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过。如等于线程池的最大大小,则表示线程池曾经满了。
- getPoolSize:线程池的线程数量。如果线程池不销毁的话,池里的线程不会自动销毁,所以这个大小只增不减
- getActiveCount:获取活动的线程数。
通过继承线程池并重写线程池的beforeExecute,afterExecute和terminated方法,可以在任务执行前,执行后和线程池关闭前干一些事情。如监控任务的平均执行时间,最大执行时间和最小执行时间等。这几个方法在线程池里是空方法。
/**
* 给定的Thread执行Runnable之前调用此方法
*
* @param t the thread that will run task {@code r}
* @param r the task that will be executed
*/
protected void beforeExecute(Thread t, Runnable r) { }
/**
* 给定的Runnable完成后执行此方法
* This method is invoked by the thread that executed the task.
* @param r the runnable that has completed
* @param t the exception that caused termination, or null if
* execution completed normally
*/
protected void afterExecute(Runnable r, Throwable t) { }
/**
* 当Executor终止时调用此方法.
* 注意:方法中子类应调用super.terminated()
*/
protected void terminated() { }
例如:
class ExtendedExecutor extends ThreadPoolExecutor {
// ...
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
if (t == null && r instanceof Future>) {
try {
Object result = ((Future>) r).get();
} catch (CancellationException ce) {
t = ce;
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt(); // ignore/reset
}
}
if (t != null)
System.out.println(t);
}
}
本文只是简单介绍了下线程池的基本使用和简单的处理流程,后期再深入源代码分析。
参考:
http://www.uml.org.cn/j2ee/201212193.asp
http://www.jiagou4.com/2015/07/726.html
http://blog.csdn.net/sayyanfu/article/details/8574098
转载请注明出处:线程池的基本使用和执行流程分析