合肥网站建设王正刚,wordpress腾讯课堂,视频制作素材网站,重庆网站建设及优化公司JAVA线程池原理详解二
一. Executor框架
Eexecutor作为灵活且强大的异步执行框架#xff0c;其支持多种不同类型的任务执行策略#xff0c;提供了一种标准的方法将任务的提交过程和执行过程解耦开发#xff0c;基于生产者-消费者模式#xff0c;其提交任务的线程相当于生…JAVA线程池原理详解二
一. Executor框架
Eexecutor作为灵活且强大的异步执行框架其支持多种不同类型的任务执行策略提供了一种标准的方法将任务的提交过程和执行过程解耦开发基于生产者-消费者模式其提交任务的线程相当于生产者执行任务的线程相当于消费者并用Runnable来表示任务Executor的实现还提供了对生命周期的支持以及统计信息收集应用程序管理机制和性能监视等机制。
Executor的UML图常用的几个接口和子类 Executor一个接口其定义了一个接收Runnable对象的方法executor其方法签名为executor(Runnable command)。
ExecutorService是一个比Executor使用更广泛的子类接口其提供了生命周期管理的方法以及可跟踪一个或多个异步任务执行状况返回Future的方法。
AbstractExecutorServiceExecutorService执行方法的默认实现。
ScheduledExecutorService一个可定时调度任务的接口。
ScheduledThreadPoolExecutorScheduledExecutorService的实现一个可定时调度任务的线程池。
ThreadPoolExecutor线程池可以通过调用Executors以下静态工厂方法来创建线程池并返回一个ExecutorService对象。
在前面介绍的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 {ListRunnable shutdownNow();boolean isTerminated();T FutureT submit(CallableT task);}另外该接口有两个重要的实现类ThreadPoolExecutor与ScheduledThreadPoolExecutor。 其中ThreadPoolExecutor是线程池的核心实现类用来执行被提交的任务而ScheduledThreadPoolExecutor是一个实现类可以在给定的延迟后运行任务或者定期执行命令。
在上一篇文章中我是使用ThreadPoolExecutor来通过给定不同的参数从而创建自己所需的线程池但是在后面的工作中不建议这种方式推荐使用Exectuors工厂方法来创建线程池
这里先来区别线程池和线程组ThreadGroup与ThreadPoolExecutor这两个概念 a、线程组就表示一个线程的集合。
b、线程池是为线程的生命周期开销问题和资源不足问题提供解决方案主要是用来管理线程。
Executors可以创建3种类型的ThreadPoolExecutorSingleThreadExecutor、FixedThreadExecutor和CachedThreadPool
a、SingleThreadExecutor单线程线程池
ExecutorService threadPool Executors.newSingleThreadExecutor();public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueueRunnable()));}我们从源码来看可以知道单线程线程池的创建也是通过ThreadPoolExecutor里面的核心线程数和线程数都是1并且工作队列使用的是无界队列。由于是单线程工作每次只能处理一个任务所以后面所有的任务都被阻塞在工作队列中只能一个个任务执行。
b、FixedThreadExecutor固定大小线程池
ExecutorService threadPool Executors.newFixedThreadPool(5);public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueueRunnable());}这个与单线程类似只是创建了固定大小的线程数量。
c、CachedThreadPool:无界线程池
ExecutorService threadPool Executors.newCachedThreadPool();public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueueRunnable());}无界线程池意味着没有工作队列任务进来就执行线程数量不够就创建与前面两个的区别是空闲的线程会被回收掉空闲的时间是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 FutureV {boolean cancel(boolean mayInterruptIfRunning);boolean isCancelled();boolean isDone();V get() throws InterruptedException, ExecutionException;V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;
}3、FutureTaskV 可取消的异步计算此类提供了对Future的基本实现仅在计算完成时才能获取结果如果计算尚未完成则阻塞get方法。
public class FutureTaskV implements RunnableFutureVpublic interface RunnableFutureV extends Runnable, FutureVFutureTask不仅实现了Future接口还实现了Runnable接口所以不仅可以将FutureTask当成一个任务交给Executor来执行还可以通过Thread来创建一个线程。
Callable与FutureTask
定义一个callable的任务
public class MyCallableTask implements CallableInteger{Overridepublic Integer call() throws Exception{System.out.println(callable do something);Thread.sleep(100);return new Random().nextInt(100);}}public class CallableTest
{public static void main(String[] args) throws Exception{CallableInteger callable new MyCallableTask();FutureTaskInteger future new FutureTaskInteger(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 get future.get());d、在第22行获取异步线程执行的结果我这个DEMO中没有执行到这里需要注意的是future.get方法会阻塞当前线程 直到任务执行完成返回结果为止。
System.out.println(future get future.get());Callable与Future
public class CallableThread implements CallableStringprivate SimpleDateFormat df new SimpleDateFormat(yyyy-MM-dd HH:mm:ss:SSS);
{Overridepublic String call()throws Exception{System.out.println(进入Call开始休眠。休眠时间 df.format(new Date(System.currentTimeMillis())));Thread.sleep(10000);return 睡觉啦;}public static void main(String[] args) throws Exception{CallableString callable new CallableThread();ExecutorService executor Executors.newSingleThreadExecutor();FutureString submit executor.submit(callable);executor.shutdown();Thread.sleep(5000);System.out.println(主线程休眠5秒。当前时间 df.format(new Date(System.currentTimeMillis())) );String s submit.get();System.out.println(Future数据已拿到str s ,当前时间 df.format(new Date(System.currentTimeMillis())));}
}执行结果
进入Call开始休眠。休眠时间2023-02-28 17:09:17:629
主线程休眠5秒。当前时间2023-02-28 17:09:22:631
Future数据已拿到str睡觉啦,当前时间2023-02-28 17:09:27:645这里的future是直接扔到线程池里面去执行的。由于要打印任务的执行结果所以从执行结果来看主线程虽然休眠了5s但是从Call方法执行到拿到任务的结果这中间的时间差正好是10s说明get方法会阻塞当前线程直到任务完成。
通过FutureTask也可以达到同样的效果
public static void main(String[] args) throws Exception{ExecutorService es Executors.newSingleThreadExecutor();CallableString call new CallableThread();FutureTaskString task new FutureTaskString(call);es.submit(task);es.shutdown();Thread.sleep(5000);System.out.println(主线程休眠5秒。当前时间 df.format(new Date(System.currentTimeMillis())) );String s submit.get();System.out.println(Future数据已拿到str s ,当前时间 df.format(new Date(System.currentTimeMillis())));}以上的组合可以给我们带来这样的一些变化
如有一种场景中方法A返回一个数据需要10s,A方法后面的代码运行需要20s但是这20s的执行过程中只有后面10s依赖于方法A执行的结果。如果与以往一样采用同步的方式势必会有10s的时间被浪费如果采用前面两种组合则效率会提高
1、先把A方法的内容放到Callable实现类的call()方法中
2、在主线程中通过线程池执行A任务
3、执行后面方法中10秒不依赖方法A运行结果的代码
4、获取方法A的运行结果执行后面方法中10秒依赖方法A运行结果的代码
这样代码执行效率一下子就提高了程序不必卡在A方法处。