线程池之Future接口
1. Future
Future接口中的get()
会阻塞当前线程直到取得Callable
的返回值
API文档
FutureTask 类是实现 Future ,实现 Runnable ,所以可以由 Executor 执行
FutureTask 可用于包装 Callable 或 Runnable 对象. 因为 FutureTask 实现 Runnable ,一个FutureTask可以提交到一个Executor执行
要想讲的清楚多线程,怎么能少得了我们的12306呢?今天,又是卖火车票的一天…
package com.ThreadPool;
import java.util.concurrent.*;
/**
* @Author: Mr.Q
* @Date: 2019-08-05 09:02
* @Description:
*/
class CallableThread implements Callable<String> {
private Integer tickets = 30;
@Override
public String call() throws Exception {
for (int i = 0; i < tickets; i++) {
if(tickets > 0) {
System.out.println(Thread.currentThread().getName() +"还剩 "+tickets--+" 票...");
}
}
return Thread.currentThread().getName() + "票卖完了!";
}
}
public class FutureTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 线程池创建线程
ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(2,5,
60,TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>());
CallableThread callableThread = new CallableThread();
// Future接口接收 Callable的返回值
Future<String> future = null;
for (int i = 0; i < 5; i++) {
// submit()提交需要返回值的任务
future = threadPoolExecutor.submit(callableThread);
}
//----------------- main - ---------------
System.out.println(future.get());
threadPoolExecutor.shutdown();
}
}
get()
会阻塞当前线程直到取得Callable
的返回值
此时,在main()
中调用get()
,导致当前线程(主线程)阻塞,直到线程池中的子线程执行完毕后,在执行!
若此时我们把 get() 放在提交线程任务时调用
Future<String> future = null;
// 单线程模式 get()会阻塞当前线程,直到有返回值为止
for (int i = 0; i < 5; i++) {
Future<String> future0 = threadPoolExecutor.submit(callableThread);
System.out.println(future0.get());
}
那么此时阻塞当前提交任务的线程,也就是说,它没有提交完任务,其他的线程都不能执行,都得等着. 表面上看我们在最大池中创建了5个线程,可实际上执行任务的只有一个,其他的都在阻塞. 此时为 单线程模式
2. FutureTask
FutureTask : 可以保证在多线程场景下,任务只会被一个线程执行,其他线程不再执行此任务;
在多线程并发下可以保证任务(传入的Callable
或 Runnable
)只执行一次
什么意思呢? 就是我现在要给在火车站柜台买票的每一个人,买一张票,我就请他去候车室喝咖啡(虽然这是不可能的)
如果,我让一个乘务员来做这件事(把乘务员抽象的看作是一个线程),他先得乘客卖票,然后再给乘客到候车室冲咖啡递上去,是不是很麻烦,效率很低?这要是到春运了,估计回家了,也就开学了…
那我再雇一个乘务员,一个负责卖票,另一个负责在候车室服务乘客,效率高,分工明确.
那么此时再看我们FutureTask
的作用
可以保证在多线程场景下,任务只会被一个线程执行,其他线程不再执行此任务
相当于负责售票的乘务员A不管服务乘客,负责服务乘客的乘务员B不管卖票;
而开始的时候,这两件事情都由乘务员C来负责,两个任务都得执行.
这就是FutureTask
的应用场景
最近也没怎么敲代码,但是头发却是哗哗的往下掉呀. 养生与学习并重,少熬夜,多运动. 然后,没事的时候,少喝点肥仔快乐水,多喝茶养生.
那我们就用FutureTask
来实现一下泡茶的场景吧
现在,两个线程负责不同的任务,并且有一个线程在执行任务后,其他线程就不再执行任务
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
/**
* @Author: Mr.Q
* @Date: 2019-08-07 09:17
* @Description:
*/
// 线程1任务
class StepOne implements Callable<String> {
private FutureTask<String> futureTask;
public StepOne(FutureTask<String> futureTask) {
this.futureTask = futureTask;
}
@Override
public String call() throws Exception {
System.out.println("T1:洗水壶.");
TimeUnit.SECONDS.sleep(1);
System.out.println("T1:烧开水..");
TimeUnit.SECONDS.sleep(10);
System.out.println("T1:等待茶叶...");
// 线程1阻塞到此处一直等到线程2返回为止
String str = futureTask.get();
System.out.println("T1:拿到茶叶....");
System.out.println("泡茶");
return "上茶~~~";
}
}
// 线程2任务
class StepTwo implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("T2:洗茶壶*");
TimeUnit.SECONDS.sleep(1);
System.out.println("T2:洗茶杯**");
TimeUnit.SECONDS.sleep(1);
System.out.println("T2:拿茶叶***");
TimeUnit.SECONDS.sleep(3);
return "龙井";
}
}
public class FutureThreadPoolDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask step2 = new FutureTask(new StepTwo());
//将线程2的龙井茶传给线程1
FutureTask step1 = new FutureTask(new StepOne(step2));
new Thread(step1).start();
new Thread(step2).start();
// 主线程在step1,step2执行完之后再执行
System.out.println(step1.get());
}
}
执行过程:
好了,今天的养生课到这里就结束了,下课吧同学们…