多线程之Runnable、Callable和Future的详解和区别
Runnable、Callable和Future的详解和区别
在程序运行中,执行的时间和用户的体验是密切相关的,人们不希望用一个经常卡顿的网站或应用,这时候多线程能给程序带来质的提升。
一、Runnable
简介
Runnable接口只有一个抽象的run()方法,此方法是在Thread.start()的时候由JVM调用run方法,创建一个线程,并调用run方法。
例子
public class RunnableTest {
public static void main(String[] args) {
Runnable runnable = () -> {
try {
System.out.println("thread1"+Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("处理完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
System.out.println("thread2"+Thread.currentThread().getName());
new Thread(runnable).start();
System.out.println("thread3"+Thread.currentThread().getName());
}
}
运行结果
thread2main
thread3main
thread1Thread-0
处理完成
结论
主线程不会等待run方法执行完成,而是直接执行完成。
二、Callable和Future
简介
Callable和Future,它俩很有意思的,一个产生结果,一个拿到结果。
Callable接口类似于Runnable,从名字就可以看出来了,但是Runnable不会返回结果,并且无法抛出返回结果的异常,而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值。
例子
public class CallableTest {
public static void main(String[] args) throws Exception {
Callable<Integer> callable2 = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("Thread1"+Thread.currentThread().getName());
Thread.sleep(1000);
return new Random().nextInt(100);
}
};
FutureTask<Integer> futureTask = new FutureTask<Integer>(callable2);
System.out.println("Thread2"+Thread.currentThread().getName());
new Thread(futureTask).start();
System.out.println(futureTask.get());
System.out.println("Thread2"+Thread.currentThread().getName());
}
}
运行结果
Thread2main
Thread1Thread-0
17
Thread2main
结论
调用futureTask的get方法会让主线程等待子线程拿到返回值再去执行下一步。但是一般不会立刻调用FutureTask的get方法,而是主线程处理其他操作,需要用到返回值再去调用。
注意
到这里可能有人会说了,调用FutureTask的get方法会让主线程等待,那不是还不如Runnable接口好?
其实这里只是夸张,把子线程计算时间设置大了点。
假设有一个很耗时的返回值需要计算,并且这个返回值不是立刻需要的话
,那么就可以使用这个组合,用另一个线程去计算返回值,而当前线程在使用这个返回值之前可以做其它的操作,等到需要这个返回值时,再通过FutureTask的get方法得到。
三、实战:Spring整合线程池,给Callable传参,并获取返回值
public class ApplicationTests {
@Resource(name = "taskExecutor")
Executor taskExecutor;
@Test
public void contextLoads() throws ExecutionException, InterruptedException {
test();
}
public void test() throws ExecutionException, InterruptedException {
List<String> list = new ArrayList<>();
List<Future<String>> results = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
list.stream().forEach(item->{
System.out.println("主线程参数:" + item);
ThreadPoolTaskExecutor threadPoolTaskExecutor =(ThreadPoolTaskExecutor) taskExecutor;
//submit的时候,callable内部的call方法已经在开始计算了。
results.add(threadPoolTaskExecutor.submit(new ApplicationTests.CallableTest(item)));
});
System.out.println("主线程执行其他任务。。。花费3秒");
Thread.sleep(3000);
System.out.println("主线程执行其他任务完毕。。。开始使用返回值");
}
class CallableTest implements Callable<String> {
private String stringArg;
public CallableTest(String item) {
System.out.println("【主线程给Callable接口的call方法传参】:"+item+",当前线程:"+Thread.currentThread().getName());
this.stringArg = item;
}
@Override
public String call() throws Exception {
String returnStr = "Hello: "+stringArg;
System.out.println("【Callable接口的call,返回值】"+returnStr+"当前线程:"+Thread.currentThread().getName());
return returnStr;
}
}
}
运行结果
主线程参数:aaa
【主线程给Callable接口的call方法传参】:aaa,当前线程:main
主线程参数:bbb
【主线程给Callable接口的call方法传参】:bbb,当前线程:main
主线程参数:ccc
【主线程给Callable接口的call方法传参】:ccc,当前线程:main
主线程参数:ddd
【主线程给Callable接口的call方法传参】:ddd,当前线程:main
主线程执行其他任务。。。花费3秒
【Callable接口的call,返回值】Hello: aaa当前线程:Executor-1
【Callable接口的call,返回值】Hello: bbb当前线程:Executor-1
【Callable接口的call,返回值】Hello: ccc当前线程:Executor-1
【Callable接口的call,返回值】Hello: ddd当前线程:Executor-1
主线程执行其他任务完毕。。。开始使用返回值
结论
子线程的运行并不会被主线程阻塞
假设有一个很耗时的返回值需要计算,并且这个返回值不是立刻需要的话
,那么就可以使用这个组合,用另一个线程去计算返回值,而当前线程在使用这个返回值之前可以做其它的操作,等到需要这个返回值时,再使用它。
注意
如果子线程Callable接口中的call方法执行时间比主线程长,如果主线程要调用Future的get方法获取返回值,那么主线程会等待子线程计算出值。
四、线程池配置
1、application.properties
#线程池配置
threadpool.corePoolSize=10
threadpool.keepAliveSeconds=10
threadpool.maxPoolSize=30
threadpool.queueCapacity=2000
2、java配置代码
@Configuration
@EnableAsync
@EnableScheduling
public class AsyncConfiguration implements AsyncConfigurer {
private final Logger log = LoggerFactory.getLogger(AsyncConfiguration.class);
@Value("${threadpool.corePoolSize}")
private int corePoolSize;
@Value("${threadpool.keepAliveSeconds}")
private int keepAliveSeconds;
@Value("${threadpool.maxPoolSize}")
private int maxPoolSize;
@Value("${threadpool.queueCapacity}")
private int queueCapacity;
@Override
@Bean(name = "taskExecutor")
public Executor getAsyncExecutor() {
System.setProperty("rocketmq.client.log.loadconfig","false");
log.debug("Creating Async Task Executor");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(corePoolSize);
executor.setKeepAliveSeconds(keepAliveSeconds);
executor.setKeepAliveSeconds(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setThreadNamePrefix("Executor-");
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
}
本文地址:https://blog.csdn.net/qq_40205337/article/details/109959474
推荐阅读
-
详解多线程及Runable 和Thread的区别
-
jquery遍历之parent()和parents()的区别及parentsUntil()方法详解
-
Python探索之静态方法和类方法的区别详解
-
基于DOM节点删除之empty和remove的区别(详解)
-
JSP之表单提交get和post的区别详解及实例
-
Java多线程下的其他组件之CyclicBarrier、Callable、Future和FutureTask详解
-
Java 继承Thread 实现Runnable接口和实现Callable接口创建线程的区别
-
jquery遍历之parent()和parents()的区别及parentsUntil()方法详解
-
多线程之Runnable、Callable和Future的详解和区别
-
jquery遍历之parent()和parents()的区别及parentsUntil()方法详解_jquery