序
上次分享了多线程与自动任务的风花雪月悲惨结局,今天借此继续分享下多线程带返回结果的基本讨论与实现。
一、使用场景
一个业务方法可能执行时间很长,而我们也不急着摇返回结果,那么就可以用多线程去开启一个子线程执行这个业务方法,同时,子线程可以返回这个业务方法的返回结果,那这是什么模式呢?
二、关于Future
1.什么是Future模式
Future模式 是多线程开发中非常常见的一种设计模式。它的核心思想是异步调用。当我们需要调用一个函数方法时。如果这个函数执行很慢,那么我们就要进行等待。但有时候,我们可能并不急着要结果。因此,我们可以让被调用者立即返回,让他在后台慢慢处理这个请求。对于调用者来说,则可以先处理一些其他任务,在真正需要数据的场合再去尝试获取需要的数据。
场景比如:外卖。 比如在午休之前我们可以提前叫外卖,只需要点好食物,下个单。然后我们可以继续工作。到了中午下班的时候外卖也就到了,然后就可以吃个午餐,再美滋滋的睡个午觉。而如果你在下班的时候才叫外卖,那就只能坐在那里干等着外卖小哥,最后拿到外卖吃完午饭,午休时间也差不多结束了。
使用Future模式 获取数据的时候无法立即得到需要的数据。而是先拿到一个契约,你可以再将来需要的时候再用这个契约去获取需要的数据,这个契约就好比叫外卖的例子里的外卖订单。
2.与用普通方式的差别
下面用一张图对比下:
三、代码简单实现
自动任务入口
import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONUtil;
import com.xiaotian.datadiver.core.Result;
import com.xiaotian.datadiver.service.ThreadService;
import com.xiaotian.datadiver.util.LicenseUtil;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
@Configuration
@EnableScheduling
@ConditionalOnProperty(prefix = "scheduling", name = "enabled", havingValue = "true")
@Slf4j
public class BusinessTask {
@Resource
private ThreadService threadService;
@Scheduled(fixedDelayString = "${scheduling.business.masterSlaveRun.fixedDelay}")
public void masterSlaveThreadRunTest() {
log.info("--多线程-主从线程与周期测试--");
String now = DateUtil.now();
long sleepTime = 5000;
try {
Future<?> ft = threadService.slaveThreadRunFuture(now,sleepTime);
threadService.slaveThreadRun(now,sleepTime);
Result<?> res = (Result<?>) ft.get();
log.info("子线程返回Future:{}", JSONUtil.toJsonStr(res));
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
log.info("--主线程运行完成,时间:{}", now);
}
}
threadService实现类
import com.xiaotian.datadiver.core.Result;
import com.xiaotian.datadiver.core.ResultGenerator;
import com.xiaotian.datadiver.service.ThreadService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class ThreadServiceImpl implements ThreadService {
@Async
@Override
public void slaveThreadRun(String now, long sleepTime) {
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
log.error("----从线程异常:{}", e.getMessage(), e);
}
log.info("线程[{}]我{}睡了{},醒了,可以继续了", Thread.currentThread().getName(), now, sleepTime);
}
@Async
@Override
public Future<?> slaveThreadRunFuture(String now, long sleepTime) {
ExecutorService es = Executors.newSingleThreadExecutor();
Future ft = es.submit(() -> {
Result<?> rs = slaveThreadRunHasResult(now, sleepTime);
return rs;
});
return ft;
}
private Result<?> slaveThreadRunHasResult(String now, long sleepTime) {
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
log.error("----从线程异常:{}", e.getMessage(), e);
}
log.info("子线程[{}]我{}睡了{},醒了,可以继续了", Thread.currentThread().getName(), now, sleepTime);
return ResultGenerator.genSuccessResult("子线程[" + now + "]运行时长[" + sleepTime + "]后返回");
}
}
代码就是这么简单,没啥好解释的,唯一就是这里用自动任务套了一层,然后主线程调用子线程方法,子线程又开启线程执行业务方法而已。在实际使用过程中,可以根据这个进行剪裁。
四、执行结果截图
主从线程打印日志,做了标记,结合自动任务入口调用顺序,自行体会下。
总结
1、Future模式思路棒棒哒 2、不得不说java的多线程操作越来越简单了 3、这里的Future是使用的Runnable,其实也可以使用Callable实现 4、需要顺便了解下ExecutorService.execute()和ExecutorService.submit()的区别(为什么这里是调用的Executor.submit()) PS: 1、接收的参数不一样 2、submit有返回值,而execute没有 原话: Method submit extends base method Executor.execute by creating and returning a Future that can be used to cancel execution and/or wait for completion. 翻译的意思: 用到返回值的例子,比如说我有很多个做validation的task,我希望所有的task执行完,然后每个task告诉我它的执行结果,是成功还是失败,如果是失败,原因是什么。然后我就可以把所有失败的原因综合起来发给调用者。不过我觉得cancel execution这个用处不大,很少有需要去取消执行的,看多个线程有一个已经出现事务问题了,会不会有这样的使用需求。
3、submit方便Exception处理 原话: There is a difference when looking at exception handling. If your tasks throws an exception and if it was submitted with execute this exception will go to the uncaught exception handler (when you don’t have provided one explicitly, the default one will just print the stack trace to System.err). If you submitted the task with submit any thrown exception, checked or not, is then part of the task’s return status. For a task that was submitted with submit and that terminates with an exception, the Future.get will rethrow this exception, wrapped in an ExecutionException. 翻译的意思: 如果你在你的task里会抛出checked或者unchecked exception,而你又希望外面的调用者能够感知这些exception并做出及时的处理,那么就需要用到submit,通过捕获Future.get抛出的异常。
最后就到这里了,希望能帮到大家。
|