线程执行器和不使用线程执行器的对比(优缺点)

1.线程执行器分离了任务的创建和执行,通过使用执行器,只需要实现Runnable接口的对象,然后把这些对象发送给执行器即可。

2.使用线程池来提高程序的性能。当发送一个任务给执行器时,执行器会尝试使用线程池中的线程来执行这个任务。避免了不断创建和销毁线程导致的性能开销。

3.执行器可以处理实现了Callable接口的任务。Callable接口类似于Runnable接口,却提供了两方面的增强:

a.Callable主方法名称为call(),可以返回结果

b.当发送一个Callable对象给执行器时,将获得一个实现了Future接口的对象。可以使用这个对象来控制Callable对象的状态和结果。

4.提供了一些操作线程任务的功能

使用线程执行器的例子

  • 执行继承了Runnable接口的任务类
声明任务类Task
 public class Task implements Runnable {
private String name; public Task(String name){
this.name=name;
}
@Override
public void run() {
}
}
使用执行器调用任务类
 public class Server {
private ThreadPoolExecutor executor; public Server(){
executor=(ThreadPoolExecutor)Executors.newCachedThreadPool();
}
public void executeTask(Task task){
System.out.printf("Server: A new task has arrived\n");
executor.execute(task);
System.out.printf("Server: Active Count: %d\n",executor.getActiveCount());
System.out.printf("Server: Completed Tasks: %d\n",executor.getCompletedTaskCount());
}
public void endServer() {
executor.shutdown();
}
}
需要注意的地方:
 1、ThreadPoolExecutor提供了好几种构造函数,由于这些构造函数的参数比较多,难于记忆,所以这里使用Executors类对其构造函数进行了封装,封装后的静        态函数可以通过函数名称更加直观的表述其含义。
2、执行实现Runnable接口的任务类使用的方式是:executor.execute(task);后面可以看到它和调用实现Callable接口的任务类还是有区别的。
3、使用执行器时要显示结束执行器。如果不关闭,那么执行器会一直执行而程序不会结束。如果执行器没有任务执行了,它将继续等待新任务的到来,而不会          结束执行。结束执行器这里使用的方式是shutdown();
  • 执行实现了Callable<T>接口的任务
 public class FactorialCalculator implements Callable<Integer> {
private Integer number;
public FactorialCalculator(Integer number){
this.number=number;
} @Override
public Integer call() throws Exception {
int num, result; num=number.intValue();
result=1; // If the number is 0 or 1, return the 1 value
if ((num==0)||(num==1)) {
result=1;
} else {
// Else, calculate the factorial
for (int i=2; i<=number; i++) {
result*=i;
Thread.sleep(20);
}
}
System.out.printf("%s: %d\n",Thread.currentThread().getName(),result);
// Return the value
return result;
}
}
交给执行器去执行:
 ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);//实例化执行器
FactorialCalculator calculator = new FactorialCalculator(number);//实例化任务
Future<Integer> result = executor.submit(calculator);//执行任务,并返回Future<T>实例
需要注意的地方:
1、Callable接口是一个泛型接口,该接口声明了call()方法。
 public interface Callable<V> {
V call() throws Exception;
}
2、执行器调用submit()方法执行任务之后,返回一个Future<T>类型对象。Future是一个异步任务的结果。意思就是任务交给执行器后,执行器就会立刻返回一个Future对象,而此时任务正在执行中。Future对象声明了一些方法来获取由Callable对象产生的结果,并管理他们的状态。Future包含的方法如下:
 

 
线程执行器的四种实例方式
前面提到由于ThreadPoolExecutor类的构造函数比较难记忆(参数多,形式也差不多),Java提供了一个工厂类Executors来实现执行器对象的创建。具体函数如下:

 
这些函数以new开头。
1、newCachedThreadPool():缓存线程池
   public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
需要注意的地方:
      如果需要执行新任务,缓存线程池就会创建新线程;如果线程所运行的任务执行完成后并且这个线程可用,那么缓存线程池将会重用这些线程。
      优点:减少了创建新线程所花费的时间
      缺点:如果任务过多,系统的负荷会过载
      使用条件:线程数量合理(不太多)或者线程运行只会运行很短的时间 
2、newFixedThreadPool():固定线程池,(fixed:固定)
 public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
需要注定的地方:
        创建了具有线程最大数量值(即线程数量 <= nThreads)的执行器。如果发送超过数量的任务给执行器,剩余的任务将被阻塞知道线程池中有可空闲的线程来处理它们。
3、newSingleThreadExecutor():单线程执行器
 public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
4、newScheduledThreadPool(int corePoolSize):定时执行器
 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
需要注意的地方:
        使用方式如下:
 ScheduledExecutorService executor=(ScheduledExecutorService)Executors.newScheduledThreadPool(1);
executor.schedule(task,i+1 , TimeUnit.SECONDS);
其中:task是实现了Callable接口的任务。schedule的参数含义:
 public <V> ScheduledFuture<V> schedule(Callable<V> callable,//即将执行的任务
long delay,//任务执行前需要等待的时间
TimeUnit unit)//时间单位

 
线程执行器提供的功能
线程任务交给执行器去执行,执行器封装了一些方法来操作执行的线程。其中涉及到的类和接口的类图如下:
执行器的类图:
Executor是一个顶层接口,提供了唯一的一个方法execute(Runnable r)。ExecutorService继承Excutor接口,是比较核心的接口。提供了执行器具有的基本方法,包括执行器的提交(submit)和终止(shutdown)以及控制任务运行的invokeAll()和invokeAny()等方法。经常用到的执行器类一般是ThreadPoolExecutor和ScheduledThreadPoolExecutor。区别就是ScheduledThreadPoolExecutor一般和线程调度有关,也就是与一些周期性操作,时间间隔、定时执行任务的操作有关。

通过Future接口可以对执行器的线程进行一些操作,例如获取线程执行完成后的结果,取消线程的执行等,涉及Future的类图如下:

接下来具体学习上面这些类的用法以及他们提供的函数的使用场景。
 
延时执行任务和周期性执行任务
涉及到这种调度的一般使用ScheduledThreadPoolExecutor类。ScheduledThreadPoolExecutor类涉及到的和调度有关的函数如下:

延时执行任务:
 ScheduledExecutorService executor=(ScheduledExecutorService)Executors.newScheduledThreadPool(1);

         for (int i=0; i<5; i++) {
Task task=new Task("Task "+i);
executor.schedule(task,i+1 , TimeUnit.SECONDS);
} executor.shutdown();
这里声明一个定时执行器,返回ScheduleExecutorService接口。然后调用schedule()方法。schedule的参数含义:
 public <V> ScheduledFuture<V> schedule(Callable<V> callable,//即将执行的任务
long delay, //任务执行前需要等待的时间
TimeUnit unit) //时间单位
周期性执行任务:
周期性执行任务和延时执行任务相似,只不过调用的方法是scheduleAtFixedRate()。
 ScheduledExecutorService executor=Executors.newScheduledThreadPool(1);

         Task task=new Task("Task");
ScheduledFuture<?> result=executor.scheduleAtFixedRate(task, 1, 2, TimeUnit.SECONDS);
其中scheduleAtFixedRate函数的参数含义:
 public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,     //将被周期性执行的任务
long initialDelay,//任务第一次执行后的延后时间
long period, //两次执行的时间周期
TimeUnit unit) { //第二和第三个参数的时间单位
需要注意的地方:
两次执行之间的周期(即period)是指任务在两次执行开始时的时间间隔。如果有一个周期性的任务需要执行5秒钟,但是却让他没三秒执行一次,那么在任务的执行过程中会将有两个任务实例同时存在。
 
对线程任务的控制
invokeAny()和invokeAll()
这两个方法在ExecutorService中声明,ExecutorService是比较核心也是比较基础的接口。所以这两个方法应该算是执行器提供的比较宽范围(下面的子类都可以用到)的方法。
编程中比较常见的问题是,当采用多个并发任务来解决一个问题时,往往只关心这些任务中的第一个结果。比如,对一个数组排序有很多种算法,可以并发启动所有算法,对于给定的数组,第一个得到排序结果的算法就是最快的算法。这种场景可以使用invokeAny()函数实现,即:运行多个任务并返回第一个结果。 
     UserValidator ldapValidator=new UserValidator("LDAP");
UserValidator dbValidator=new UserValidator("DataBase"); // Create two tasks for the user validation objects
TaskValidator ldapTask=new TaskValidator(ldapValidator, username, password);
TaskValidator dbTask=new TaskValidator(dbValidator,username,password); // Add the two tasks to a list of tasks
List<TaskValidator> taskList=new ArrayList<>();
taskList.add(ldapTask);
taskList.add(dbTask); // Create a new Executor
ExecutorService executor=(ExecutorService)Executors.newCachedThreadPool();
String result;
try {
// Send the list of tasks to the executor and waits for the result of the first task
// that finish without throw and Exception. If all the tasks throw and Exception, the
// method throws and ExecutionException.
result = executor.invokeAny(taskList);
System.out.printf("Main: Result: %s\n",result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} // Shutdown the Executor
executor.shutdown();
其中UserValidator类睡眠一个随机模拟校验任务
 public boolean validate(String name, String password) {
Random random=new Random(); try {
Long duration=(long)(Math.random()*10);
System.out.printf("Validator %s: Validating a user during %d seconds\n",this.name,duration);
TimeUnit.SECONDS.sleep(duration);
} catch (InterruptedException e) {
return false;
} return random.nextBoolean();
}
接下来是invokeAll(),invokeAll()方法接收一个任务列表,然后返回任务列表的所有任务的执行结果。
 List<Task> taskList = new ArrayList<>();
for (int i = 0; i < 3; i++) {
Task task = new Task("Task-" + i);
taskList.add(task);
}
// Call the invokeAll() method
List<Future<Result>> resultList = null;
try {
resultList = executor.invokeAll(taskList);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Finish the executor
executor.shutdown();
Future和FutureTask
Future接口用来对接收任务执行完成后的结果以及对交给执行器执行的任务进行控制。接口提供的函数如下:

cancel()、isCancelled()
cancel()方法用来取消交给执行器的任务。isCancelled()方法用来判断是否取消成功。其中cancel(boolean)接收一个boolean类型的参数,用来表示是否要取消任务。具体用法:
 Task task=new Task();

         Future<String> result=executor.submit(task);

         try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.printf("Main: Cancelling the Task\n");
result.cancel(true);
线程交给执行器执行后会立即返回一个FutureTask<T>对象,(例如:java.util.concurrent.FutureTask@610455d6),通过调用cancel(true)方法显示来取消执行器中正在运行的任务。
注意的地方:
1、如果任务已经完成,或者之前已被取消,或者由于某种原因不能取消,则方法将返回false。
2、如果任务在执行器中等待分配Thread对象来执行它,那么任务被取消,并且不会开始执行。
3、如果任务已经在运行,那么依赖于调用cancel()方法时传递的参数。如果传递的参数为true,并且任务正在执行,任务将会取消。如果传递的参数为false并且任务正在执行,任务不会被取消。
4、如果Future对象所控制已经被取消,那么使用Future对象的get()方法将抛出CalcellationException异常控制任务的完成
isDone()
任务完成,返回值为boolean类型
get()、get(long,TimeUnit)
get()方法一直等待直到Callable对象的call()方法执行完成并返回结果。如果get()方法在等待结果时线程中断了,则将抛出一个InterruptException异常。如果call()方法抛出异常那么get()方法也将随之抛出ExecutionException异常。
get(long timeout,TimeUnit unit):如果调用这个方法时,任务的结果并未准备好,则方法等待所指定的timeout时间。如果等待超过了时间而任务的结果还没准备好,那么这个方法返回null。
思考:get()方法用来接收call()函数的返回值,因为call()函数是交由线程执行的,所以会等到所有线程执行完毕后才能得到正确的执行结果。所以在线程没有执行完成时,get()方法将一直阻塞。
FutureTask中的get()方法实现:可以看到,如果状态为非完成,则调用函数awaitDone()等待完成。
 public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
FutureTask:done()
FutureTask是Future的实现类,除了实现Future的功能外,有一个done()方法需要注意:用来控制执行器中任务的完成

done()方法允许在执行器中的任务执行结束后,还可以执行一些后续操作。可以用来产生报表,通过邮件发送结果或者释放一些系统资源。当任务执行完成是受FutureTask类控制时,这个方法在内部被FutureTask类调用。在任务结果设置后以及任务的状态已改为isDone()之后,无论任务是否被取消或者正常结束,done()方法才被调用。
默认情况下,done()方法的实现为空,我们可以覆盖FutureTask类并实现done()方法来改变这种行为。
 public class ResultTask extends FutureTask<String> {
@Override
protected void done() {
if (isCancelled()) {
System.out.printf("%s: Has been cancelled\n",name);
} else {
System.out.printf("%s: Has finished\n",name);
}
}
}
CompletionService和ExecutorCompletionService
CompletionService:完成服务
当向Executor提交批处理任务时,并且希望在它们完成后获得结果,如果用FutureTask,你可以循环获取task,并用future.get()去获取结果,但是如果这个task没有完成,你就得阻塞在这里,这个实效性不高,其实在很多场合,其实你拿第一个任务结果时,此时结果并没有生成并阻塞,其实在阻塞在第一个任务时,第二个task的任务已经早就完成了,显然这种情况用future task不合适的,效率也不高。
自己维护list和CompletionService的区别:

1.从list中遍历的每个Future对象并不一定处于完成状态,这时调用get()方法就会被阻塞住,如果系统是设计成每个线程完成后就能根据其结果继续做后面的事,这样对于处于list后面的但是先完成的线程就会增加了额外的等待时间。

2.而CompletionService的实现是维护一个保存Future对象的BlockingQueue。只有当这个Future对象状态是结束的时候,才会加入到这个Queue中,take()方法其实就是Producer-Consumer中的Consumer。它会从Queue中取出Future对象,如果Queue是空的,就会阻塞在那里,直到有完成的Future对象加入到Queue中。

CompletionService采取的是BlockingQueue<Future<V>>无界队列来管理Future。则有一个线程执行完毕把返回结果放到BlockingQueue<Future<V>>里面。就可以通过completionServcie.take().get()取出结果。
类图如下:
对于批处理任务,完成服务一方面负责去执行(submit),一方面通过take()或者poll()方法可以获取已完成的任务,任务列表中有任务完成,结果就会返回。
 
处理被执行器拒绝的任务(RejectExecutionHandler)
当我们想结束执行器的执行时,调用shutdown()方法来表示执行器应该结束。但是,执行器只有等待正在运行的任务或者等待执行的任务结束后,才能真正的结束。
如果在shutdown()方法与执行器结束之间发送了一个任务给执行器,这个任务会被拒绝,因为这个时间段执行器已经不再接受任务了。ThreadPoolExecutor类提供了一套机制,当任务被拒绝时调用这套机制来处理它们。
 public class RejectedTaskController implements RejectedExecutionHandler {

     @Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.printf("RejectedTaskController: The task %s has been rejected\n",r.toString());
System.out.printf("RejectedTaskController: %s\n",executor.toString());
System.out.printf("RejectedTaskController: Terminating: %s\n",executor.isTerminating());
System.out.printf("RejectedTaksController: Terminated: %s\n",executor.isTerminated());
}
}
先提交一个任务,然后shutdown(),接着提交另外一个任务
 public static void main(String[] args) {
// Create the controller for the Rejected tasks
RejectedTaskController controller=new RejectedTaskController();
// Create the executor and establish the controller for the Rejected tasks
ThreadPoolExecutor executor=(ThreadPoolExecutor)Executors.newCachedThreadPool();
executor.setRejectedExecutionHandler(controller); // Lauch three tasks
System.out.printf("Main: Starting.\n");
for (int i=0; i<3; i++) {
Task task=new Task("Task"+i);
executor.submit(task);
} // Shutdown the executor
System.out.printf("Main: Shuting down the Executor.\n");
executor.shutdown();
// Send another task
System.out.printf("Main: Sending another Task.\n");
Task task=new Task("RejectedTask");
executor.submit(task); // The program ends
System.out.printf("Main: End.\n"); }
执行结果如下:
Main: Starting.
Main: Shuting down the Executor.
Main: Sending another Task.
RejectedTaskController: The task java.util.concurrent.FutureTask@60e53b93 has been rejected
RejectedTaskController: java.util.concurrent.ThreadPoolExecutor@5e2de80c[Shutting down, pool size = 3, active threads = 3, queued tasks = 0, completed tasks = 0]
RejectedTaskController: Terminating: true
RejectedTaksController: Terminated: false
Main: End.
Task Task1: Starting
Task Task0: Starting
Task Task2: Starting
Task Task1: ReportGenerator: Generating a report during 4 seconds
Task Task0: ReportGenerator: Generating a report during 7 seconds
Task Task2: ReportGenerator: Generating a report during 6 seconds
Task Task1: Ending
Task Task2: Ending
Task Task0: Ending
如果执行器调用了shutdown()方法后,原本执行的任务会执行完毕。

java并发之线程执行器(Executor)的更多相关文章

  1. java并发之线程池的使用

    背景 当系统并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要消耗大量的系统资源. 所以需要一个办法使得线程可以 ...

  2. Java并发之——线程池

    一. 线程池介绍 1.1 简介 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡 ...

  3. Java并发之线程中断

    前面的几篇文章主要介绍了线程的一些最基本的概念,包括线程的间的冲突及其解决办法,以及线程间的协作机制.本篇主要来学习下Java中对线程中断机制的实现.在我们的程序中经常会有一些不达到目的不会退出的线程 ...

  4. Java并发之线程管理(线程基础知识)

    因为书中涵盖的知识点比较全,所以就以书中的目录来学习和记录.当然,学习书中知识的时候自己的思考和实践是最重要的.说到线程,脑子里大概知道是个什么东西,但很多东西都还是懵懵懂懂,这是最可怕的.所以想着细 ...

  5. Java并发之线程转储

    一.java线程转储 java的线程转储可以被定义为JVM中在某一个给定的时刻运行的所有线程的快照.一个线程转储可能包含一个单独的线程或者多个线程.在多线程环境中,比如J2EE应用服务器,将会有许多线 ...

  6. Java 并发之线程安全

    写线程安全的代码,说白了就是管理一个类的共享的.可变的状态.只要有多于 1 个线程对类的状态进行写入,那么就必须用同步来协调这多个线程对状态的访问.对于一个没有状态的类来说(简单的理解就是只有方法没有 ...

  7. Java并发之线程异常捕获

    由于线程的本质特性,使得你不能捕获从线程中逃逸的异常,如: import java.util.concurrent.ExecutorService; import java.util.concurre ...

  8. Java并发之线程

    在前面我们介绍的一些内容中,我们的程序都是一条执行流,一步一步的执行.但其实这种程序对我们计算机的资源的使用上是低效的.例如:我们有一个用于计算的程序,主程序计算数据,在计算的过程中每得到一个结果就需 ...

  9. Java并发之线程间的协作

    上篇文章我们介绍了synchronized关键字,使用它可以有效的解决我们多线程所带来的一些常见问题.例如:竞态条件,内存可见性等.并且,我们也说明了该关键字主要是一个加锁和释放锁的集成,所有为能获得 ...

随机推荐

  1. mybatis的搭建和注入spring的方式

    mybatis实际上是一个更多关注sql语句的框架,他的出现是想让开发者更简单的去操作数据库. 与hibernate相比较,hibernate更多的是去sql化,虽然hibernate也可以本地sql ...

  2. python基础条件和循环

    一.if语句 1.if后表达式返回值为true则执行其子代码块,然后此if 语句到此终结,否则进入下一分支判断,直到满足其中一个分支,执行后终结if 2.expression可以引入运算符:not,a ...

  3. Spring装配Bean之Java代码装配bean

    尽管通过组件扫描和自动装配实现Spring的自动化配置很方便也推荐,但是有时候自动配置的方式实现不了,就需要明确显示的配置Spring.比如说,想要将第三方库中的组件装配到自己的应用中,这样的情况下, ...

  4. UVa1630,Folding

    区间dp,记忆化搜就可以 st为原串 dp[p][q]存st[p]~st[q]的最优长度,f[p][q]存对应的最优串 从(0,len-1)开始搜,f[0][len-1]为所求ans,回溯条件为p== ...

  5. 【转】深度分析NandFlash—物理结构及地址传送(以TQ2440开发板上的K9F2G08U0A为例)

    K9F2G08U0A是三星公司生产的总容量为256M的NandFlash,常用于手持设备等消费电子产品.还是那句话,搞底层就得会看datasheet,我们就从它的datasheet看起. 这就是 K9 ...

  6. Nginx-OpenResty安装配置

    上两篇中介绍了: Ngnix技术研究系列1-通过应用场景看Nginx的反向代理 Ngnix技术研究系列2-基于Redis实现动态路由 发现,应该加一篇OpenResty的安装部署说明,方便大家按图索骥 ...

  7. eclipse项目中引入shiro-freemarker-tags会jar包冲突

    maven项目中引入了这个依赖. <dependency> <groupId>net.mingsoft</groupId> <artifactId>sh ...

  8. SSM(Spring+SpringMVC+Mybatis)框架搭建详细教程【附源代码Demo】

    [前言] 应某网络友人邀约,需要一个SSM框架的Demo作为基础学习资料,于是乎,就有了本文.一个从零开始的SSM框架Demo对一个新手来说,是非常重要的,可大大减少在学习过程中遇到的各种各样的坑,说 ...

  9. LAMP 实现全过程及wordpress的搭建

    一.介绍 1. LAM(M)P: L:linux A:apache (httpd) M:mysql, mariadb M:memcached 缓存 P:php, perl, python WEB 资源 ...

  10. spacemacs 初始安装报错

    尝试使用emcas的配置文件spacemacs,第一次安装启动时,界面为纯白色,而且在输入完几个配置选项后,报了一个错误 Symbol's value as variable is void 根据官网 ...