java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 程序设计语言和 Java 平台(即 JavaEE(j2ee), JavaME(j2me), JavaSE(j2se))的总称。
这篇文章主要介绍了 Java 线程池原理深入分析的相关资料, 需要的朋友可以参考下
Java 线程池原理
Executor 框架的两级调度模型
在 HotSpot VM 的模型中,Java 线程被一对一映射为本地操作系统线程。JAVA 线程启动时会创建一个本地操作系统线程,当 JAVA 线程终止时,对应的操作系统线程也被销毁回收,而操作系统会调度所有线程并将它们分配给可用的 CPU。
在上层,JAVA 程序会将应用分解为多个任务,然后使用应用级的调度器(Executor)将这些任务映射成固定数量的线程;在底层,操作系统内核将这些线程映射到硬件处理器上。
Executor 框架类图
在前面介绍的 JAVA 线程既是工作单元,也是执行机制。而在 Executor 框架中,我们将工作单元与执行机制分离开来。Runnable 和 Callable 是工作单元(也就是俗称的任务),而执行机制由 Executor 来提供。这样一来 Executor 是基于生产者消费者模式的,提交任务的操作相当于生成者,执行任务的线程相当于消费者。
1、从类图上看,Executor 接口是异步任务执行框架的基础,该框架能够支持多种不同类型的任务执行策略。
- public interface Executor {
- void execute(Runnable command);
- }
Executor 接口就提供了一个执行方法,任务是 Runnbale 类型,不支持 Callable 类型。
2、ExecutorService 接口实现了 Executor 接口,主要提供了关闭线程池和 submit 方法:
- public interface ExecutorService extends Executor {
- List<Runnable> shutdownNow();
- boolean isTerminated();
- <T> Future<T> submit(Callable<T> task);
- }
另外该接口有两个重要的实现类:ThreadPoolExecutor 与 ScheduledThreadPoolExecutor。
其中 ThreadPoolExecutor 是线程池的核心实现类,用来执行被提交的任务;而 ScheduledThreadPoolExecutor 是一个实现类,可以在给定的延迟后运行任务,或者定期执行命令。
在上一篇文章中,我是使用 ThreadPoolExecutor 来通过给定不同的参数从而创建自己所需的线程池,但是在后面的工作中不建议这种方式,推荐使用 Exectuors 工厂方法来创建线程池
这里先来区别线程池和线程组(ThreadGroup 与 ThreadPoolExecutor)这两个概念:
a、线程组就表示一个线程的集合。
b、线程池是为线程的生命周期开销问题和资源不足问题提供解决方案,主要是用来管理线程。
Executors 可以创建 3 种类型的 ThreadPoolExecutor:SingleThreadExecutor、FixedThreadExecutor 和 CachedThreadPool
a、SingleThreadExecutor:单线程线程池
- ExecutorService threadPool = Executors.newSingleThreadExecutor();
- public static ExecutorService newSingleThreadExecutor() {
- return new FinalizableDelegatedExecutorService
- (new ThreadPoolExecutor(1, 1,
- 0L, TimeUnit.MILLISECONDS,
- new LinkedBlockingQueue<Runnable>()));
- }
我们从源码来看可以知道,单线程线程池的创建也是通过 ThreadPoolExecutor,里面的核心线程数和线程数都是 1,并且工作队列使用的是无界队列。由于是单线程工作,每次只能处理一个任务,所以后面所有的任务都被阻塞在工作队列中,只能一个个任务执行。
b、FixedThreadExecutor:固定大小线程池
- ExecutorService threadPool = Executors.newFixedThreadPool(5);
- public static ExecutorService newFixedThreadPool(int nThreads) {
- return new ThreadPoolExecutor(nThreads, nThreads,
- 0L, TimeUnit.MILLISECONDS,
- new LinkedBlockingQueue<Runnable>());
- }
这个与单线程类似,只是创建了固定大小的线程数量。
c、CachedThreadPool: 无界线程池
- ExecutorService threadPool = Executors.newCachedThreadPool();
- public static ExecutorService newCachedThreadPool() {
- return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
- 60L, TimeUnit.SECONDS,
- new SynchronousQueue<Runnable>());
- }
无界线程池意味着没有工作队列,任务进来就执行,线程数量不够就创建,与前面两个的区别是:空闲的线程会被回收掉,空闲的时间是 60s。这个适用于执行很多短期异步的小程序或者负载较轻的服务器。
Callable、Future、FutureTash 详解
Callable 与 Future 是在 JAVA 的后续版本中引入进来的,Callable 类似于 Runnable 接口,实现 Callable 接口的类与实现 Runnable 的类都是可以被线程执行的任务。
三者之间的关系:
Callable 是 Runnable 封装的异步运算任务。
Future 用来保存 Callable 异步运算的结果
FutureTask 封装 Future 的实体类
1、Callable 与 Runnbale 的区别
a、Callable 定义的方法是 call,而 Runnable 定义的方法是 run。
b、call 方法有返回值,而 run 方法是没有返回值的。
c、call 方法可以抛出异常,而 run 方法不能抛出异常。
2、Future
Future 表示异步计算的结果,提供了以下方法,主要是判断任务是否完成、中断任务、获取任务执行结果
- public interface Future<V> {
- boolean cancel(boolean mayInterruptIfRunning);
- boolean isCancelled();
- boolean isDone();
- V get() throws InterruptedException, ExecutionException;
- V get(long timeout, TimeUnit unit)
- throws InterruptedException, ExecutionException, TimeoutException;
- }
3、FutureTask
可取消的异步计算,此类提供了对 Future 的基本实现,仅在计算完成时才能获取结果,如果计算尚未完成,则阻塞 get 方法。
- public class FutureTask < V > implements RunnableFuture < V >
- public interface RunnableFuture<V> extends Runnable, Future<V>
FutureTask 不仅实现了 Future 接口,还实现了 Runnable 接口,所以不仅可以将 FutureTask 当成一个任务交给 Executor 来执行,还可以通过 Thread 来创建一个线程。
Callable 与 FutureTask
定义一个 callable 的任务:
- public class MyCallableTask implements Callable<Integer>
- {
- @Override
- public Integer call()
- throws Exception
- {
- System.out.println("callable do somothing");
- Thread.sleep(5000);
- return new Random().nextInt(100);
- }
- }
- public class CallableTest
- {
- public static void main(String[] args) throws Exception
- {
- Callable<Integer> callable = new MyCallableTask();
- FutureTask<Integer> future = new FutureTask<Integer>(callable);
- Thread thread = new Thread(future);
- thread.start();
- Thread.sleep(100);
- //尝试取消对此任务的执行
- future.cancel(true);
- //判断是否在任务正常完成前取消
- System.out.println("future is cancel:" + future.isCancelled());
- if(!future.isCancelled())
- {
- System.out.println("future is cancelled");
- }
- //判断任务是否已完成
- System.out.println("future is done:" + future.isDone());
- if(!future.isDone())
- {
- System.out.println("future get=" + future.get());
- }
- else
- {
- //任务已完成
- System.out.println("task is done");
- }
- }
- }
执行结果:
- callable do somothing
- future is cancel:true
- future is done:true
- task is done
这个 DEMO 主要是通过调用 FutureTask 的状态设置的方法,演示了状态的变迁。
a、第 11 行,尝试取消对任务的执行,该方法如果由于任务已完成、已取消则返回 false,如果能够取消还未完成的任务,则返回 true,该 DEMO 中由于任务还在休眠状态,所以可以取消成功。
- future.cancel(true);
b、第 13 行,判断任务取消是否成功:如果在任务正常完成前将其取消,则返回 true
- System.out.println("future is cancel:" + future.isCancelled());
c、第 19 行,判断任务是否完成:如果任务完成,则返回 true,以下几种情况都属于任务完成:正常终止、异常或者取消而完成。
我们的 DEMO 中,任务是由于取消而导致完成。
- System.out.println("future is done:" + future.isDone());
d、在第 22 行,获取异步线程执行的结果,我这个 DEMO 中没有执行到这里,需要注意的是,future.get 方法会阻塞当前线程, 直到任务执行完成返回结果为止。
- System.out.println("future get=" + future.get());
Callable 与 Future
- public class CallableThread implements Callable<String>
- {
- @Override
- public String call()
- throws Exception
- {
- System.out.println("进入Call方法,开始休眠,休眠时间为:" + System.currentTimeMillis());
- Thread.sleep(10000);
- return "今天停电";
- }
- public static void main(String[] args) throws Exception
- {
- ExecutorService es = Executors.newSingleThreadExecutor();
- Callable<String> call = new CallableThread();
- Future<String> fu = es.submit(call);
- es.shutdown();
- Thread.sleep(5000);
- System.out.println("主线程休眠5秒,当前时间" + System.currentTimeMillis());
- String str = fu.get();
- System.out.println("Future已拿到数据,str=" + str + ";当前时间为:" + System.currentTimeMillis());
- }
- }
执行结果:
- 进入Call方法,开始休眠,休眠时间为:1478606602676
- 主线程休眠5秒,当前时间1478606608676
- Future已拿到数据,str=今天停电;当前时间为:1478606612677
这里的 future 是直接扔到线程池里面去执行的。由于要打印任务的执行结果,所以从执行结果来看,主线程虽然休眠了 5s,但是从 Call 方法执行到拿到任务的结果,这中间的时间差正好是 10s,说明 get 方法会阻塞当前线程直到任务完成。
通过 FutureTask 也可以达到同样的效果:
- public static void main(String[] args) throws Exception {
- ExecutorService es = Executors.newSingleThreadExecutor();
- Callable < String > call = new CallableThread();
- FutureTask < String > task = new FutureTask < String > (call);
- es.submit(task);
- es.shutdown();
- Thread.sleep(5000);
- System.out.println("主线程等待5秒,当前时间为:" + System.currentTimeMillis());
- String str = task.get();
- System.out.println("Future已拿到数据,str=" + str + ";当前时间为:" + System.currentTimeMillis());
- }
以上的组合可以给我们带来这样的一些变化:
如有一种场景中,方法 A 返回一个数据需要 10s,A 方法后面的代码运行需要 20s,但是这 20s 的执行过程中,只有后面 10s 依赖于方法 A 执行的结果。如果与以往一样采用同步的方式,势必会有 10s 的时间被浪费,如果采用前面两种组合,则效率会提高:
1、先把 A 方法的内容放到 Callable 实现类的 call() 方法中
2、在主线程中通过线程池执行 A 任务
3、执行后面方法中 10 秒不依赖方法 A 运行结果的代码
4、获取方法 A 的运行结果,执行后面方法中 10 秒依赖方法 A 运行结果的代码
这样代码执行效率一下子就提高了,程序不必卡在 A 方法处。
来源: http://www.phperz.com/article/17/1221/358383.html