异步编程学习之路(六)-Future和Callable原理及使用

本文是异步编程学习之路(六)-通过Future和Callable实现数据批量处理,若要关注前文,请点击传送门:

异步编程学习之路(五)-线程池原理及使用

前文我们讲解了通过Thread和Runnable实现多线程的方式,如果要A线程与B线程协同合作,我们就需要用到共享变量和线程之间的通信方法,比如wait()、notify()、notifyAll()等,用到这些通信方法的主要原因是不管Thread还是Runnable都不能获得返回结果,而自从Java 1.5开始,就提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果。

本文给大家介绍一下Future、Callable的原理以及使用。

一、Future源码分析

Futrue模式:对于多线程,如果线程A要等待线程B的结果,那么线程A没必要等待B,直到B有结果,可以先拿到一个未来的Future,等B有结果是再取真实的结果。

在多线程中经常举的一个例子就是:网络图片的下载,刚开始是通过模糊的图片来代替最后的图片,等下载图片的线程下载完图片后在替换。而在这个过程中可以做一些其他的事情。

Future接口中提供了五种方法,方便开发人员操作,代码如下:

1、cancel(取消任务)

/**
 * Attempts to cancel execution of this task.  This attempt will
 * fail if the task has already completed, has already been cancelled,
 * or could not be cancelled for some other reason. If successful,
 * and this task has not started when {@code cancel} is called,
 * this task should never run.  If the task has already started,
 * then the {@code mayInterruptIfRunning} parameter determines
 * whether the thread executing this task should be interrupted in
 * an attempt to stop the task.
 *
 * <p>After this method returns, subsequent calls to {@link #isDone} will
 * always return {@code true}.  Subsequent calls to {@link #isCancelled}
 * will always return {@code true} if this method returned {@code true}.
 *
 * @param mayInterruptIfRunning {@code true} if the thread executing this
 * task should be interrupted; otherwise, in-progress tasks are allowed
 * to complete
 * @return {@code false} if the task could not be cancelled,
 * typically because it has already completed normally;
 * {@code true} otherwise
 */
boolean cancel(boolean mayInterruptIfRunning);

cancel方法是试图取消此任务的执行,如果任务已经完成、已经取消或由于其他原因无法取消,则此尝试将失败,如果已经取消则返回true,否则返回false。

2、isCancelled(判断任务是否被取消)

/**
 * Returns {@code true} if this task was cancelled before it completed
 * normally.
 *
 * @return {@code true} if this task was cancelled before it completed
 */
boolean isCancelled();

isCancelled方法是判断人物是否被取消,如果被取消则返回true,否则返回false。

3、isDone(判断任务是否完成)

/**
 * Returns {@code true} if this task completed.
 *
 * Completion may be due to normal termination, an exception, or
 * cancellation -- in all of these cases, this method will return
 * {@code true}.
 *
 * @return {@code true} if this task completed
 */
boolean isDone();

isDone方法是判断任务是否完成,如果任务被正常终止、抛出异常或者被取消,以上情况都会返回true,false返回false。

4、get(获取线程返回结果)

/**
 * Waits if necessary for the computation to complete, and then
 * retrieves its result.
 *
 * @return the computed result
 * @throws CancellationException if the computation was cancelled
 * @throws ExecutionException if the computation threw an
 * exception
 * @throws InterruptedException if the current thread was interrupted
 * while waiting
 */
V get() throws InterruptedException, ExecutionException;

get方法是用来获取Future返回结果的,该方法会阻塞主线程的执行直到Future结果返回,此方法可能会造成程序死等,一般推荐使用get的另一个重载方法。

5、get(功能同上,可以设置超时时间)

/**
 * Waits if necessary for at most the given time for the computation
 * to complete, and then retrieves its result, if available.
 *
 * @param timeout the maximum time to wait
 * @param unit the time unit of the timeout argument
 * @return the computed result
 * @throws CancellationException if the computation was cancelled
 * @throws ExecutionException if the computation threw an
 * exception
 * @throws InterruptedException if the current thread was interrupted
 * while waiting
 * @throws TimeoutException if the wait timed out
 */
V get(long timeout, TimeUnit unit)
	throws InterruptedException, ExecutionException, TimeoutException;

get的重载方法,在给定的超时时间内阻塞主线程等待Future结果返回,如果存在则返回结果,不存在则返回null。

二、Callable源码分析

/**
 * A task that returns a result and may throw an exception.
 * Implementors define a single method with no arguments called
 * {@code call}.
 *
 * <p>The {@code Callable} interface is similar to {@link
 * java.lang.Runnable}, in that both are designed for classes whose
 * instances are potentially executed by another thread.  A
 * {@code Runnable}, however, does not return a result and cannot
 * throw a checked exception.
 *
 * <p>The {@link Executors} class contains utility methods to
 * convert from other common forms to {@code Callable} classes.
 *
 * @see Executor
 * @since 1.5
 * @author Doug Lea
 * @param <V> the result type of method {@code call}
 */
@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

Callable接口返回的结果可能抛出异常,继承者定义了一个单例的没有参数的方法被称为call 。Callable接口与Runnable接口相似,这两个都是为那些可能被其他线程的执行的实例的类设计的 ,Runnable不会返回结果也不会抛出异常,Callable则可以。

三、Future批量处理数据入库

    现在存在这样一个业务场景,我们需要将表A查询出来的省市区id替换为具体名称并分批入库,我们的实现思路如下:

  • 首先我们直接取本地省市区表中的数据,将取出来的数据按照省市区三级做一个处理得出结果M。

  • 在查询出结果M的同时通过主线程查询表A的相关的信息。

  • 然后我们需要使用结果M将表A查询出来的省市区id替换为具体的地区名称。

  • 在替换的过程中我们同时监控它替换后的数量,每50条批量入库一次。

1、ThreadPoolTest

/**
 * @Description:通过Future和Callable实现数据批量处理
 * @Author:zhangzhixiang
 * @CreateDate:2018/12/25
 * @Version:1.0
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ThreadPoolTest {
    
    /**
     * 省市区<id, name>
     */
    public static Map<Integer, String> provinceMap = Maps.newHashMap();
    public static Map<Integer, String> cityMap = Maps.newHashMap();
    public static Map<Integer, String> areaMap = Maps.newHashMap();

    @Autowired
    private ProvinceDAO provinceDAO;

    @Autowired
    private ClueInfoDAO clueInfoDAO;

    @Test
    public void test() throws Exception {
        //1、定义线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100), new ThreadPoolExecutor.DiscardPolicy());
        //2、异步获取省市区数据
        Callable<List<ProvinceDO>> provinceCallable = () -> {
            List<ProvinceDO> provinceDoList = provinceDAO.getProvince();
            for(ProvinceDO data : provinceDoList) {
                if (data.getLevel() == 1) {
                    provinceMap.put(data.getId(), data.getAreaName());
                }
                if (data.getLevel() == 2) {
                    cityMap.put(data.getId(), data.getAreaName());
                }
                if (data.getLevel() == 3) {
                    areaMap.put(data.getId(), data.getAreaName());
                }
            }
            return null;
        };
        //3、获取省份Future值
        Future<List<ProvinceDO>> provinceFuture = threadPoolExecutor.submit(provinceCallable);
        //4、异步获取线索表数据
        Callable<List<ClueInfoDO>> clueCallable = () -> clueInfoDAO.selectByCondition(null);
        //5、获取线索Future值
        Future<List<ClueInfoDO>> clueFuture = threadPoolExecutor.submit(clueCallable);
        //6、组装并替换省市区
        List<ClueInfoDO> realClueList = clueFuture.get(15, TimeUnit.SECONDS);
        if (provinceFuture.isDone()) {
            List<Arrangement> arrangementList = Lists.newArrayList();
            for (ClueInfoDO clueInfoDO : realClueList) {
                Arrangement arrangement = new Arrangement();
                arrangement.setClueName(clueInfoDO.getName());
                if (clueInfoDO.getProvince() != null) {
                    arrangement.setProvinceName(provinceMap.get(clueInfoDO.getProvince().getId()));
                }
                if (clueInfoDO.getCity() != null) {
                    arrangement.setCityName(cityMap.get(clueInfoDO.getCity().getId()));
                }
                if (clueInfoDO.getArea() != null) {
                    arrangement.setAreaName(areaMap.get(clueInfoDO.getArea().getId()));
                }
                arrangementList.add(arrangement);
            }
            int runSize = 50;
            int handleSize = arrangementList.size() / runSize;
            try {
                List<Arrangement> newList = null;
                CountDownLatch countDownLatch = new CountDownLatch(runSize);
                for (int i = 0; i < runSize + 1; i++) {
                    if (i == runSize) {
                        int startIndex = i * handleSize;
                        int endIndex = arrangementList.subList(startIndex, endIndex);
                        newList = arrangementList.subList(startIndex, endIndex);
                    } else {
                        int startIndex = i * handleSize;
                        int endIndex = (i + 1) * handleSize;
                        newList = arrangementList.subList(startIndex, endIndex);
                    }
                    threadPoolExecutor.execute(new ArrangementRunnable(newList, countDownLatch));
                }
                countDownLatch.await();
                threadPoolExecutor.shutdown();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }   
    }
}

2、ArrangementRunnable

/**
 * @Description:入库
 * @Author:zhangzhixiang
 * @CreateDate:2018/12/25
 * @Version:1.0
 */
public class ArrangementRunnable implements Runnable {
    
    private List<Arrangement> list;
    
    private CountDownLatch countDownLatch;

    public ArrangementRunnable(Lise<Arrangement> list, CountDownLatch countDownLatch) {
        this.list = list;
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        if (list != null) {
            try {
                ArrangementDAO dao = SpringHelper.getBeanByClass(ArrangementDAO.class);
                dao.batchIntset(list);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                countDownLatch.countDown();
            }
        }
    }
}

3、Spring帮助类

/**
 * @Description:Spring帮助类
 * @Author:zhangzhixaing
 * @CreateDate:2018/08/31 16:39:45
 * @Version:1.0
 */
@Component
public class SpringHelper implements ApplicationContextAware {
 
    private static ApplicationCOntext applicationContext = null;
 
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if(SpringHelper.applicationContext == null) {
            SpringHelper.applicationContext = applicationContext;
        }
    }
    
    /**
     * 根据一个bean的id获取配置文件中相应的bean
     */
    public stativ Object getBean(String beanId) throws BeansException {
        if(applicationContext.containsBean(beanId)) {
            applicationContext.getBean(beanId);
        }
        return null;
    }
    
    /**
     * 根据一个bean的类型获取配置文件中相应的bean
     */ 
    public static <T> T getBeanByClass(Class<T> requiredType) throws BeansException {
        return applicationContext.getBean(requiredType);
    }
 
    /**
     * 如果BeanFactory包含一个与所给名称匹配的bean的定义,则返回true,否则false
     */
    public static boolean containsBean(String name) {
        return applicationContext.containsBean(name);
    }
    
    /**
     * 获取Spring容器
     */
    public static ApplicationContext getApplicationContext() {
        return SpringHelper.applicationContest;
    }
}

本文涉及到一些与数据库相关的DAO,这些我就不做展示了,大家只要知道Future和Calable如何配合使用和批量入库,本文的目的就达到了。

注意:在Runnable实现类中不能使用@Autowire做依赖注入,注入不进去会是null,我在这里通过Spring帮助类来实例化相关DAO实体。

本文到此结束,之后的文章就开始学习Lock锁,它被认为是替代synchronize锁的更好的选择。

异步编程学习之路(七)-Lock的原理及使用

发布了352 篇原创文章 · 获赞 390 · 访问量 37万+

猜你喜欢

转载自blog.csdn.net/qq_19734597/article/details/85237645