本文是异步编程学习之路(六)-通过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锁的更好的选择。