Callable和Runnable同步异步的使用
Runnable我们都用过,因为开启一个新的线程的时候,需要传递一个Runnable接口作为执行对象,这个接口里面只包含一个run方法,无参数,无返回值。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
而Callable和Runnable类似,但是有返回值。Callable里面只有一个方法:
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
execute方法
首先介绍ExecutorService类的execute方法,该方法来自于父类Executor的方法,入参为Runnable,无返回值。所以该方法适用于开启线程做异步操作,无需等待返回结果及超时时间。
public interface Executor {
/**
* Executes the given command at some time in the future. The command
* may execute in a new thread, in a pooled thread, or in the calling
* thread, at the discretion of the {@code Executor} implementation.
*
* @param command the runnable task
* @throws RejectedExecutionException if this task cannot be
* accepted for execution
* @throws NullPointerException if command is null
*/
void execute(Runnable command);
}
使用如下:
@Value("${detailThreadPool.sync.corePoolSize:20}")
private int syncCorePoolSize;
@Value("${detailThreadPool.sync.maximumPoolSize:250}")
private int syncMaximumPoolSize;
@Value("${detailThreadPool.sync.keepAliveTime:2000}")
private int syncKeepAliveTime;
@Value("${detailThreadPool.sync.queueSize:100}")
private int syncQueueSize;
private ExecutorService asyncExecutor;
@PostConstruct
public void init() {
asyncExecutor = new ThreadPoolExecutor(asyncCorePoolSize, asyncMaximumPoolSize, asyncKeepAliveTime, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(asyncQueueSize),
new ThreadFactory() {
AtomicInteger count = new AtomicInteger(0);
public Thread newThread(Runnable r) {
Thread th = new Thread(r, "asyncUserDetail-" + count.getAndIncrement());
return th;
}
},
new ThreadPoolExecutor.AbortPolicy());
}
// ExecutorService异步处理,具体操作可以写业务类继承Runnable接口,并在构造方法传入具体业务参数等等
public void asyncExecutor(){
asyncExecutor.execute(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(i);
}
}
});
asyncExecutor.shutdown();
}
submit方法
submit属于ExecutorService类的方法,有两个构造法方法,一个用来提交Runnable对象,一个用来提交Callable对象。这些方法都是返回一个Future的对象。所以该方法适用于开启线程做同步操作
我们可以来看一下:
public interface ExecutorService extends Executor {
@NotNull <T> Future<T> submit(@NotNull Callable<T> task);
@NotNull <T> Future<T> submit(@NotNull Runnable task,T result);
@NotNull Future<?> submit(@NotNull Runnable task)
}
使用如下:
@Value("${detailThreadPool.sync.corePoolSize:20}")
private int syncCorePoolSize;
@Value("${detailThreadPool.sync.maximumPoolSize:250}")
private int syncMaximumPoolSize;
@Value("${detailThreadPool.sync.keepAliveTime:2000}")
private int syncKeepAliveTime;
@Value("${detailThreadPool.sync.queueSize:100}")
private int syncQueueSize;
private ExecutorService syncExecutor;
@PostConstruct
public void init() {
syncExecutor = new ThreadPoolExecutor(syncCorePoolSize, syncMaximumPoolSize, syncKeepAliveTime, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(syncQueueSize),
new ThreadFactory() {
AtomicInteger count = new AtomicInteger(0);
public Thread newThread(Runnable r) {
Thread th = new Thread(r, "syncUserDetail-" + count.getAndIncrement());
return th;
}
},
new ThreadPoolExecutor.AbortPolicy());
}
// ExecutorService同步处理,具体操作可以写业务类继承Callable接口,并在构造方法传入具体业务参数等等
public void syncExecutor() throws Exception {
Future<Integer> sumFuture = syncExecutor.submit(new Callable<Integer>() {
@Override
public Integer call() {
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
}
});
// 此处主线程可以先处理其他事情,在来调用.get()方法,处理异步结果的内容。
System.out.println(sumFuture.get());
}
注意:
向线程池中提交任务的submit方法不是阻塞方法,而Future.get方法是一个阻塞方法,当submit提交多个任务时,只有所有任务都完成后,才能使用get按照任务的提交顺序得到返回结果,所以一般需要使用future.isDone先判断任务是否全部执行完成,完成后再使用future.get得到结果。(也可以用get (long timeout, TimeUnit unit)方法可以设置超时时间,防止无限时间的等待)
三段式的编程:1.启动多线程任务2.处理其他事3.收集多线程任务结果,Future虽然可以实现获取异步执行结果的需求,但是它没有提供通知的机制,要么使用阻塞,在future.get()的地方等待future返回的结果,这时又变成同步操作;要么使用isDone()轮询地判断Future是否完成,这样会耗费CPU的资源。
Future、CompletionService和CompletableFuture区别可以参考文章:future.get方法阻塞问题的解决,实现按照任务完成的先后顺序获取任务的结果_fourierr的博客-CSDN博客_future.get
其中Future为同步阻塞、CompletionService为同步非阻塞、CompletableFuture为异步非阻塞
CompletableFuture创建异步任务的两种方式:
(1)创建任务
##使用runAsync方法新建一个线程来运行Runnable对象(无返回值);
CompletableFuture.runAsync(() -> {
List<Long> seqList = list.getRecords().stream().filter(e -> !e.getIsRead()).map(e -> e.getSeqId()).collect(Collectors.toList());
if (seqList.size() > 0) {
log.info(">>>>>> 异步更新未读记录");
batchSetRecordIsRead(seqList);
log.info("<<<<<< 异步更新未读记录完成,size:{}", seqList.size());
}
});
##使用supplyAysnc方法新建线程来运行Supplier<T>对象(有返回值);
CompletableFuture<Integer> result = CompletableFuture.supplyAsync(() -> {
List<Long> seqList = list.getRecords().stream().filter(e -> !e.getIsRead()).map(e -> e.getSeqId()).collect(Collectors.toList());
if (seqList.size() > 0) {
log.info(">>>>>> 异步更新未读记录");
batchSetRecordIsRead(seqList);
log.info("<<<<<< 异步更新未读记录完成,size:{}", seqList.size());
}
return 5;
});
##基于线程池创建
Future总结(同步、异步使用)
同步可以用submit方法:
- 原理,主线程阻塞等待线程池执行结果,从共享变量获取结果
- 使用场景
- 需要程序提高执行速度,并且需要获取执行结果
- 需要有超时等待任务执行完毕,超时获取结果
异步可以用execute方法:
只需要提交一个任务(不需要超时等待),且主线程(main)提交任务后无操作