5.5.2 FutureTask
FutureTask也可以用作闭锁。(FutureTask实现了Future语义,表示一种抽象的可生成结果的计算)。FutureTask表示的计算是通过Callable来实现的,相当于一种可生成结果的Runnable,有三种状态:执行完成【正常结束、取消、异常】。
Future.get的行为取决于任务的状态。如果任务已经完成,那么get会立即返回结果,否则get将阻塞直到任务进入完成状态,然后返回结果或者抛出异常。
// 支持泛型
@FunctionInterface
public interface Callable<V>{
V call() throws Exception;
}
Callable一般是配合线程池工具ExecutorService来使用。
Future接口只有几个简单的方法;
public abstract interface Future<V>{
public abstract boolean cancel(boolean paramBoolean);
public abstract boolean isCancel();
public abstract boolean isDone();
public abstract V get() throws InterruptedException, ExecutionException;
public abstract V get(long paramLong, TimeUnit paramTimeUnit)
throws InterruptedException, ExecutionException, TimeoutException;
}
注意:cancel方法只是试图取消一个线程的执行,并不一定能够取消成功。
有时候,为了让任务有能够取消的功能,就使用Callable来代替Runnable,可以声明Future<?>形式类型,并返回null作为底层任务的结果。
FutureTask实现的RunnableFuture接口,而RunnableFuture接口又同时继承了Runnable接口和Future接口:
public interface RunnableFuture<V> extends Runnable, Future<V>{
void run();
}
由于Future只是一个接口,而它里面的cancel、get、isDone等方法要自己实现起来都是很复杂的,所以JDK提供了一个FutureTask类供使用。
class Task implements Callable<Integer>{
@override
public Integer call() throws Exception{
Thread.sleep(1000);
return 2;
}
public static void main(String ars[]){
ExecutorService executor = Executor.newCachedThreadPool();
Task task = new Task();
Future<Integer> result = executor.submit(task); // 这个方法执行有返回值
//调用get方法会阻塞当前的线程,直到得到结果
// 所有实际编码中建议可以设置超时间的重载get方法。
System.out.println(result.get());
}
}
~output:
2
// 与上面的区别:调用submit方法是没有返回值的,这里实际调用的submit(Runnable task)方法,而上面的Demo,调用的是submit(Callable<T> task)方法,在许多高并发的环境下,有可能Callable和FutureTask会创建多次,FutureTask能够在高并发环境下确保任务只执行一次。
class Task implements Callable<Integer>{
@Override
public Integer call() throws Exception{
Thread.sleep(1000);
return 2;
}
public static void main(String args[]){
ExecutorService executor = Executor.newCachedThreadPool();
FutureTask<Integer> futureTask = new FutureTask<>(new Task());
executor.submit(futureTask); // 这个方法没有返回值
System.out.println(futureTask.get());
}
}
5.5.3 信号量
计数信号量用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的次数。计数信号量还可以用来实现某种资源池,或者对容器施加边界。可以使用volatile变量来实现“信号量”的模型。
应用场景:
多个线程(超过2个)需要相互合作,我们用简单的“锁”和“等待通知机制”就不那么方便了。这个时候就可以用到信号量。在JDK中提供的很多线程通信工具类都是基于信号量模型的。
package chapter05;
public class Signal {
private static volatile int signal = 0;
static class ThreadA implements Runnable{
@Override
public void run() {
while(signal < 5){
if (signal % 2 == 0){
System.out.println("threadA: " + signal);
synchronized (this){
signal ++;
}
}
}
}
}
static class ThreadB implements Runnable{
@Override
public void run() {
while(signal < 5){
if (signal % 2 == 1){
System.out.println("threadB: " + signal);
synchronized (this){
signal = signal +1;
}
}
}
}
}
public static void main(String[] args) throws InterruptedException{
new Thread(new ThreadA()).start();
Thread.sleep(1000);
new Thread(new ThreadB()).start();
}
}
output~:
threadA: 0
threadB: 1
threadA: 2
threadB: 3
threadA: 4