1.执行器
并发API提供了一种称为执行器(线程池)的特性,用于启动和控制线程的执行,因此,执行器为线程的管理提供了一种替代方案。
执行器的核心是Executor接口。主要方法有:void execute(Runnable thread):用于启动一个线程;ExecutorService接口扩展了Executor接口,添加了用于帮助管理和控制线程执行的方法:void shutdown():用于关闭执行器,如果执行器不被关闭,则会一直运行,程序不会终止,除非显式关闭了执行器。ScheduledExecutorService接口,该接口扩展了ExecutorService接口,用于支持线程调度。
并发API提供的执行器实例类总共3个:ThreadPoolExecutor(实现了Executor接口和ExecutorService接口)、ScheduledThreadPoolExecutor(实现了Executor和ScheduledExecutorService接口)、ForkJoinPool。
除了ForkJoinPool在后续博文介绍Fork/Join框架的时候探讨,这里对前两个执行器类做实例代码展示。
ThreadPoolExecutor实例代码展示:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorServiceTest1 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.execute(()->{
System.out.println("thread-0 running");
});
executorService.execute(()->{
System.out.println("thread-1 running");
});
executorService.execute(()->{
System.out.println("thread-2 running");
});
executorService.execute(()->{
System.out.println("thread-3 running");
});
executorService.execute(()->{
System.out.println("thread-4 running");
});
//如果不调用Executor的shutdown方法,程序并不会终止,因为执行器仍在运行
executorService.shutdown();
}
// 运行结果:
// thread-0 running
// thread-1 running
// thread-2 running
// thread-3 running
// thread-4 running
}
ScheduledThreadPoolExecutor实例代码如下:
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorServiceTest {
public static void main(String[] args) {
/*
* 用于定时执行任务
*/
ScheduledExecutorService ses = Executors.newScheduledThreadPool(3);
//初始延迟是1秒,然后每隔两秒执行一次
ses.scheduleWithFixedDelay(new ThreadForExecutorServiceTest2(ses), 1, 2, TimeUnit.SECONDS);
}
}
class ThreadForExecutorServiceTest2 implements Runnable{
int i = 5;
ScheduledExecutorService ses;
public ThreadForExecutorServiceTest2(ScheduledExecutorService ses) {
this.ses = ses;
}
@Override
public void run() {
while(i>0){
i--;
System.out.println("running... "+i);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果不关闭执行器,程序不会停止
ses.shutdown();
}
}
2.Callable接口和Future接口
其实,看到这里,就能发现一个问题:至此使用实现Runnable接口(Thread类也是实现了Runnable接口)的方式创建的线程是没有返回值的,也就是线程执行完了就是执行完了,如果在线程执行完了想返回点东西给调用线程,这就用到了Callable接口和Future接口。Callable接口用于创建有返回值的线程,Future接口用于接收返回值。具体的API分析如下:
Callable接口是泛型接口:interface Callable<V>:V指明任务返回的数据类型,这个接口只有一个方法:V call()在call方法中定义希望执行的任务,在任务执行完成后返回结果,如果不能计算结果,call方法必须抛出异常。
Callable任务通过ExecutorService接口的submit方法执行。submit方法有三种方式,其中一种用于执行Callable任务:<T> Future<T> submit(Callable<T> task):task是要执行的任务,Future用于接收返回值。
Future接口是泛型接口:interface Future<V>:V指明要接收的结果数据类型。为了获取返回值,使用下面两种方法获取:V get()、V get(long wait,TimeUnit tu),一种是无限制的等待,一种是添加了等待超时的等待。
使用这两个接口创建有返回值线程的实例代码如下:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableFutureTest {
public static void main(String[] args){
//获取线程池
ExecutorService executorService = Executors.newFixedThreadPool(2);
//执行可以有返回值的线程1
Future<Double> future = executorService.submit(new Callable<Double>(){
@Override
public Double call() throws Exception {
double sum=0;
for(int i = 1;i<=10;i++){
sum+=i;
}
return sum;
}
});
Future<String> future2 = executorService.submit(new Callable<String>(){
@Override
public String call() throws Exception {
//在这个线程中获取上一个线程的返回值,并使用,如果执行的时候还没有出结果,那就等,直到出结果
if(future.get()==55.0){
System.out.println("Got the return valre of last thread...");
}
return "the second thread returned..";
}
});
try {
System.out.println(future2.get());
} catch (InterruptedException | ExecutionException e) {
System.out.println("Exception happend..");
e.printStackTrace();
}
executorService.shutdown();
}
/*
* 因为由实现Runnable接口来创建的线程,线程任务都是由run方法中的内容决定的,而run方法没有返回值
* 需求来咯:如果我想在线程运行完了之后返回一个东西给调用线程呢?这就要用到Callable接口和Future接口了
*/
// 运行结果:
// Got the return valre of last thread...
// the second thread returned..
}
3.时间单位TimeUnit
并发API定义了一些方法,这些方法的参数中使用了TimeUnit类型,用于指明超时时间。TimeUnit是用于指定计时单位(或时间粒度)的枚举。尽管在调用使用TimeUnit的方法时,TimeUnit可以指定任何粒度的值,但是并不能保证系统是否能够达到指定的粒度。
使用TimeUnit的示例代码如下:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class TimeUnitTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
Future<Double> future = executorService.submit(new Callable<Double>() {
@Override
public Double call(){
int sum = 0;
for(int i = 0;i<=10;i++){
sum+=i;
}
try {
//故意让着线程睡一会,然后另外一个线程就在特定时间内拿不到结果,超时后不再等待
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return (double) sum;
}
});
Future<String> future2 = executorService.submit(new Callable<String>() {
@Override
public String call(){
double d = 0.0;
try {
d = future.get(10,TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
if(e instanceof TimeoutException)
//确定是发生了超时异常
System.out.println("Exception happend...");
}
if(d==55.0){
return "Got the return value of the first thread";
}
return "Time out:Got null from the first thread";
}
});
try {
System.out.println(future2.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
executorService.shutdown();
}
/*
* 是时间单位类,譬如线程等待的时候,可以指明等待的时间,或者等线程的返回结果,也有等待时间
* 超过了这个时间,就不再等待了,超时没有拿到东西或者超时等待了,就要抛出超时异常,毕竟指定时间内没给我答案
*/
//运行结果:
// Exception happend...
// Time out:Got null from the first thread
}