目录
1、多线程的实现方式
在JDK1.5以前,多线程的两种创建方式,实现Runnable接口,或者继承Thread类;其中Thread类也是实现了Runnable接口;重写run方法,完成多线程要执行的内容;如下源码可以看到,run方法可以执行完成任务,但是方法返回确实void类型;
class Thread implements Runnable {
}
public interface Runnable {
public abstract void run();
}
2、第三种创建线程方式
为了解决多线程返回值的问题,在concurrent包中,有了第三种线程的实现方式:Callable(中译:可偿还的)接口,看翻译就知道不是执行一下那么简单,可重写call方法,获取线程执行返回值;
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
执行Callable以后,会通过Future(中译:未来)的get方法来获取返回值,看翻译就知道肯定跟后来有点什么关系:
public interface Future<V> {
/**a、假设任务未开始,调用cancel(true/false)方法将返回false;
b、假设任务已经开始,执行cancel(true)方法将以中断执行此任务线程
的方式来试图停止,中断成功,返回true;
c、假设当任务已经启动,执行cancel(false)方法将不会对正在执行的
任务线程产生影响(让线程正常执行到完成),返回false;
d、当任务已经完成,执行cancel(true/false)方法将返回false;
*/
boolean cancel(boolean mayInterruptIfRunning);
/**如果任务执行完成之前,被取消成功,则返回true
*/
boolean isCancelled();
/**a、线程正常执行完成,返回true;
b、线程抛出异常,返回true;
c、线程被取消,返回true;
/
boolean isDone();
/* a、阻塞直等到线程执行任务完成返回计算结果返回计算结果;
* b、如果任务被取消,抛出CancellationException;
* c、如果任务计算中途异常,抛出ExecutionException;
* d、如果线程被中断,抛出InterruptedException;
*/
V get() throws InterruptedException, ExecutionException;
/**参考上述get方法,唯一不同的是,加上了时间戳,超过时间戳未执
行完成的话,抛出TimeoutException /
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
3、执行任务Demo
Future的唯一实现类是FutureTask,常见的方式,就是我们把Callable、FutureTask和线程池结合一起来使用:
/**
* 测试Future和Callable
*/
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class TestFutureTaskDemo {
//方便展示运行,这里写一个内部类实现Callable类,重写call方法
private class CallableDemo implements Callable<Integer> {
//线程执行结果
private Integer result;
//线程名称,方便查看多个线程执行进度
private String callableName;
public CallableDemo(Integer result,String callableName) {
this.result = result;
this.callableName = callableName;
}
@Override
public Integer call() throws Exception {
result = result + 1;
System.out.println("callableName:"
+callableName+"; result :"+result);
Thread.sleep(5*1000);
return result;
}
}
public static void main(String[] args) {
//单个线程计算结果初始值
Integer computeResult = 5;
TestFutureTaskDemo task = new TestFutureTaskDemo();
// 创建一个futureList来存放线程池提交Callable任务返回的Future对象;
List<Future<Integer>> futureList = new ArrayList<Future<Integer>>();
// 创建线程池,来执行Callable任务,核心线程大小数3;
// 阿里巴巴Java编码规范,不推荐使用Executors来创建线程池;
// 例如:Executors.newFixedThreadPool(3);
// 原规则描述如下:【强制】线程池不允许使用 Executors
// 去创建,而是通过 ThreadPoolExecutor 的方式,这样
// 的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
ExecutorService executor =
new ThreadPoolExecutor(3, 3,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
//需要n个线程计算,n=6;
for (int i = 0; i < 6; i++) {
Future f = executor.submit(task.new
CallableDemo(computeResult,i+""));
// f.cancel(false);
futureList.add(f);
}
// 假设执行其它任务2秒过后,开始尝试中断线程
try {
Thread.sleep(2*1000);
} catch (InterruptedException e) {
System.out.println("main thread InterruptedException");
}
//取消第一个执行中的线程
//这里进行了cancel(true/false),
//下面的 Integer temp = ft.get() 代码,就会抛
//CancellationException异常
System.out.println("callable "+0+" is canceled:"
+futureList.get(0).cancel(true));
// 如果把上面的中断代码去掉
//正常运行结果应该是5+(6*(5+1))=41
for (Future<Integer> ft : futureList) {
try {
Integer temp = ft.get();
computeResult = computeResult+temp;
} catch (InterruptedException e) {
System.out.println("demo1 InterruptedException");
} catch (ExecutionException e) {
System.out.println("demo1 ExecutionException");
}
}
System.out.println("demo1 computeResult:"+computeResult);
executor.shutdown();
}
}
运行结果,因为任务被取消了,所以会抛出异常,注释掉任务取消的代码,可以尝试运行一下代码,那么会返回总执行结果Integer值41:
callableName:2; result :6
callableName:0; result :6
callableName:1; result :6
callable 0 is canceled:true
callableName:3; result :6
Exception in thread "main" java.util.concurrent.CancellationException
at java.util.concurrent.FutureTask.report(FutureTask.java:121)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at interview.concurrent.TestFutureTaskDemo.main
(TestFutureTaskDemo.java:74)
callableName:4; result :6
callableName:5; result :6
4、应用场景
由于Future是常用的多线程设计模式,因此在JDK中内置了Future模式的实现。这些类都在java.util.concurrent包里面。
对于Future模式来说,虽然它无法立即给你需要的数据。但是,它会返回给你一个契约,将来,你可以凭借这个契约去重新获取你需要的信息。
通过上面的例子,我们可以发现,Future模式,可以用于哪些应用场景呢?
(1)、在一些实际应用中,某些操作很耗时,但又不可或缺。APP浏览新闻时,先显示的是文字内容,而图片的下载肯定要比文字慢,图片重要性略低于文字,
可以用Future模式,文字和图片异步并行执行下载;
(2)购物时,查看以前的订单信息;可能要查许多关联数据:订单详情,积分信息,物流信息等,所以可以用Future做出异步多线程的计算后显示(有专门的CountDownLatch,貌似实现这种场景更合适一些);
(3)再者,查一个数据集合,第一页至第一百页,需要返回总结集。如果一次limit 0 10000,这样,一个SQL查询出非常慢。但用100个线程,一个线程只查limit0 10 就非常快了,利用多线程的特性,返回多个集合,再合并集合结果;(思考,查询10次可能和数据库交互10次I/O,真的就比一次查询快吗?当然这里只是举例一个可选择场景)
(4)、Future用于异步获取执行结果或者取消任务。在高并发场景下确保任务只执行一次。
在很多高并发的环境下,往往我们只需要某些任务只执行一次。这种使用情景FutureTask的特性恰能胜任。举一个例子,假设有一个带key的连接池,当key存在时,即直接返回key对应的对象;当key不存在时,则创建连接。对于这样的应用场景,通常采用的方法为使用一个Map对象来存储key和连接池对应的对应关系,典型的代码如下面所示:
应用(4)场景参考贴如下:https://blog.csdn.net/linchunquan/article/details/22382487
下一篇: