多线程Future模式使用

上次分享了多线程与自动任务的风花雪月悲惨结局,今天借此继续分享下多线程带返回结果的基本讨论与实现。


一、使用场景

一个业务方法可能执行时间很长,而我们也不急着摇返回结果,那么就可以用多线程去开启一个子线程执行这个业务方法,同时,子线程可以返回这个业务方法的返回结果,那这是什么模式呢?

二、关于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;

/**
 * 业务自动任务
 *
 * @author zhengwen
 **/
@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();
    //执行完成后间隔3秒,设置睡5秒钟,设置睡5秒钟
    long sleepTime = 5000;
    //threadService.slaveThreadRun(now, sleepTime);
    try {
    
    
      Future<?> ft = threadService.slaveThreadRunFuture(now,sleepTime);
      //主线程继续干其他事情
      threadService.slaveThreadRun(now,sleepTime);

      //拿带返回Future结果的线程里的返回数据,这里get到的就是业务方法里返回的对象,与同步调用返回的对象一样样的
      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;

/**
 * @author zhengwen
 **/
@Slf4j
@Service
public class ThreadServiceImpl implements ThreadService {
    
    

  @Async
  @Override
  public void slaveThreadRun(String now, long sleepTime) {
    
    
    //主线程是1分钟执行一次,这里我就让睡1分半钟
    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对象
      Result<?> rs = slaveThreadRunHasResult(now, sleepTime);
      return rs;
    });
    return ft;
  }


  private Result<?> slaveThreadRunHasResult(String now, long sleepTime) {
    
    
    //主线程是1分钟执行一次,这里我就让睡1分半钟
    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抛出的异常。

最后就到这里了,希望能帮到大家。

猜你喜欢

转载自blog.csdn.net/zwrlj527/article/details/123638963