FutureTask——另一种闭锁的实现

关于多线程,我们接触对多的,最基础,入门的可能就是实现Runnable接口继承Thead类,因为Java单继承的原因,通常建议是实现Runnable接口。但这种“简单”的线程会带来一个问题,写过的人都知道,不管是实现Runnable还是继承Thread类,他们都是实现public void run()方法,而这个方法可以看到是没有返回值的。但我们想执行一个线程得到它的返回值应该怎么做呢?此时FutureTask就要出场了。

首先我们还是需要定义一个线程,不同的是这个线程需要实现Callable接口。这是第一个不同的地方。

 1 package futuretask;
 2
 3 import java.util.concurrent.Callable;
 4
 5 /**
 6  * Created by yulinfeng on 12/17/16.
 7  */
 8 public class Task implements Callable<String> {
 9     private String name;
10
11     public Task(String name){
12         this.name = name;
13     }
14
15     @Override
16     public String call() throws Exception {
17         String hello = hello(name);
18         return hello;
19     }
20
21     public String hello(String name){
22         return "hello " + name;
23     }
24 }

可以看到此时并不是实现run方法,而是一个叫call的方法,同时有返回值(返回值取决于所定义的泛型)。定义好这个线程过后,并不是直接将此线程的实例传入一个Thread类中执行start方法,这也是和没有返回值的线程不同的地方。

下面我们通过两种方法使用FutureTask,一个是通过线程池的方式创建线程,一个是直接创建一个线程。



通过多线程的方式创建线程:

 1 package futuretask;
 2
 3 import countdownlatch.TaskThread;
 4
 5 import java.util.concurrent.*;
 6
 7 /**
 8  * Created by yulinfeng on 12/17/16.
 9  */
10 public class Test {
11     public static void main(String[] args){
12         ExecutorService exec = Executors.newCachedThreadPool();     //创建一个线程池
13         Future<String> hello = exec.submit(new Task("kevin"));  //执行线程池的submit方法,执行callable线程,并返回一个Future对象
14         try {
15             System.out.println(hello.get());    //通过调用Future的get方法,获取线程的返回值
16         } catch (InterruptedException e) {
17             e.printStackTrace();
18         } catch (ExecutionException e) {
19             e.printStackTrace();
20         }
21     }
22 }

这是第一种通过线程池创建线程的方式,通过调用线程的submit方法。如果是实现的Runnable方法,通过线程池创建线程执行的则是线程池的execute方法。一定注意调用线程返回值时使用的是Future的get方法来获取的返回值。



直接创建线程:

 1 package futuretask;
 2
 3 import countdownlatch.TaskThread;
 4
 5 import java.util.concurrent.*;
 6
 7 /**
 8  * Created by yulinfeng on 12/17/16.
 9  */
10 public class Test {
11     public static void main(String[] args){
12         FutureTask<String> future = new FutureTask<String>(new Task("kevin"));  //将线程传入FuturaTask的构造方法中
13         Thread thread = new Thread(future);
14         thread.start();
15         try {
16             System.out.println(future.get());
17         } catch (InterruptedException e) {
18             e.printStackTrace();
19         } catch (ExecutionException e) {
20             e.printStackTrace();
21         }
22     }
23 }

在调用get方法时,一定是在执行完线程后,不然将会一直阻塞。

其实仔细观察以上两张方法,第一种是Future接口,第二种是FutureTask类。FutureTask是Future接口的实现,为什么不能将Future接口传入Thread的构造方法中呢?原因很简单。在Thread的单个参数构造方法中,只能传入Runnable类型,而Future并没有继承Runnable接口,FutureTask则同时继承了Runnable接口和Future接口。

最后,那么FutureTask应该怎么来定义呢?其实和我们上一节将的CountDownLatch一样,它也是一种闭锁的实现。Future.get取决于任务的状态,如果任务已完成,那么get会立即返回结果,否则get将阻塞直到任务进入完成状态,然后返回结果或者抛出异常。FutureTask将计算结果从执行计算的线程传递到获取这个结果的线程,而FutureTask的规范确保了这种传递过程能实现结果的安全发布。——《Java并发编程实践》

时间: 2024-11-10 01:22:48

FutureTask——另一种闭锁的实现的相关文章

深入浅出 Java Concurrency (10): 锁机制 part 5 闭锁 (CountDownLatch)[转]

此小节介绍几个与锁有关的有用工具. 闭锁(Latch) 闭锁(Latch):一种同步方法,可以延迟线程的进度直到线程到达某个终点状态.通俗的讲就是,一个闭锁相当于一扇大门,在大门打开之前所有线程都被阻断,一旦大门打开所有线程都将通过,但是一旦大门打开,所有线程都通过了,那么这个闭锁的状态就失效了,门的状态也就不能变了,只能是打开状态.也就是说闭锁的状态是一次性的,它确保在闭锁打开之前所有特定的活动都需要在闭锁打开之后才能完成. CountDownLatch是JDK 5+里面闭锁的一个实现,允许一

Callable、Future和FutureTask浅析

我们知道创建线程的方式有两种,一种是实现Runnable接口,另一种是继承Thread,但是这两种方式都有个缺点,那就是在任务执行完成之后无法获取返回结果,那如果我们想要获取返回结果该如何实现呢?还记上一篇Executor框架结构中提到的Callable接口和Future接口吗?,是的,从JAVA SE 5.0开始引入了Callable和Future,通过它们构建的线程,在任务执行完成后就可以获取执行结果,今天我们就来聊聊线程创建的第三种方式,那就是实现Callable接口. 1.Callabl

[转]FutureTask详解

FutureTask类是Future 的一个实现,并实现了Runnable,所以可通过Excutor(线程池) 来执行,也可传递给Thread对象执行.如果在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成,当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态. Executor框架利用FutureTask来完成异步任务,并可以用来进行任何潜在的耗时的计算.一般FutureTask多用于耗时的计算,主线程可以在完成自

【Java多线程】Future与FutureTask

一:Future 在使用实现Callable创建线程时,call()方法是有返回值的.那么,我们在编程时用什么来代表这个 线程执行后才能返回的未来结果 呢?那就是 Future类型. 顾名思义,Future--未来值,我们用这个未来值来代替编程中需要用到线程结果的地方,然后在实际运行时,通过 future.get() 方法来获取线程的真正运行结果. Future接口有一个泛型参数,其类型与call()方法的返回值类型要一致,也就是说,Future<V>  只是代表 V类型的未来值 而已,不是真

Java多线程编程:Callable、Future和FutureTask浅析

通过前面几篇的学习,我们知道创建线程的方式有两种,一种是实现Runnable接口,另一种是继承Thread,但是这两种方式都有个缺点,那就是在任务执行完成之后无法获取返回结果,那如果我们想要获取返回结果该如何实现呢?还记上一篇Executor框架结构中提到的Callable接口和Future接口吗?,是的,从Java SE 5.0开始引入了Callable和Future,通过它们构建的线程,在任务执行完成后就可以获取执行结果,今天我们就来聊聊线程创建的第三种方式,那就是实现Callable接口.

疯狂Java学习笔记(66)-----------Callable、Future和FutureTask

在前面的文章中我们讲述了创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口. 这2种方式都有一个缺陷就是:在执行完任务之后无法获取执行结果. 如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦. 而自从Java 1.5开始,就提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果. 今天我们就来讨论一下Callable.Future和FutureTask三个类的使用方法.以下是本文的目录

java并发编程实战笔记

1.复合操作 若一个类里有多个属性状态,对每个属性使用atomic类修饰,并且一个属性更新,要在同一原子操作内更新其他所有属性,这样才是线程安全类.需要整体类的状态操作是原子的. 要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量. 判断同步代码块的合理大小,要权衡安全性.简单性和性能. 当执行时间较长的计算或可能无法快速完成的操作(如网络IO.控制台IO)一定不要持有锁. 2.对象的共享 1)可见性 为了确保所有线程都能看到共享变量的最新值,所有执行读操作或写操作的线程都必须在同

Java 并发编程(四)常用同步工具类

同步工具类可以使任何一种对象,只要该对象可以根据自身的状态来协调控制线程的控制流.阻塞队列可以作为同步工具类,其他类型的同步工具类还包括:信号量(Semaphore).栅栏(Barrier)以及闭锁(Latch). 闭锁 首先我们来介绍闭锁. 闭锁作用相当于一扇门:在闭锁到达某一状态之前,这扇门一直是关闭的,所有的线程都会在这扇门前等待(阻塞).只有门打开后,所有的线程才会同时继续运行. 闭锁可以用来确保某些活动直到其它活动都完成后才继续执行,例如: 1.确保某个计算在其所有资源都被初始化之后才

java 并发编程读书笔记

1.同步容器,同步容器包括Vector和HashTable,是早期jdk的一部分.另一部分是同步包装类,以Collections.synchronizedxxx的工厂方法创建. 2.同步容器虽然是线程安全的,但是对于复合操作,有时你可能需要加上额外的客户端加锁进行保护,即对于使用这些容器的客户端代码,如果存在复合操作,还是可能存在风险. 3.例如check-and-act操作.循环中的元素操作等,如果在客户端代码中没有额外的锁,都会发生意想不到的问题. 4.造成这些的问题都可以通过在客户端加锁来