1. 环境

Java: jdk1.8.0_144

2. 背景

Java多线程执行任务时,Logback输出的主线程和各个子线程的业务日志需要区分时,可以根据线程池和执行的线程来区分,但若要把它们联系起来只能根据时间线,既麻烦又无法保证准确性。

2018-10-27 23:09:22 [INFO][com.lxp.tool.log.LogAndCatchExceptionRunnableTest][main][testRun][38] -> test start
2018-10-27 23:09:22 [INFO][com.lxp.tool.log.RunnabeTestHelper][pool-1-thread-1][lambda$getRunnable$0][16] -> This is runnable.
2018-10-27 23:09:22 [INFO][com.lxp.tool.log.RunnabeTestHelper][pool-1-thread-2][lambda$getRunnable$0][16] -> This is runnable.
2018-10-27 23:09:22 [INFO][com.lxp.tool.log.RunnabeTestHelper][pool-1-thread-1][lambda$getRunnable$0][16] -> This is runnable.
2018-10-27 23:09:22 [INFO][com.lxp.tool.log.RunnabeTestHelper][pool-1-thread-2][lambda$getRunnable$0][16] -> This is runnable.
2018-10-27 23:09:22 [INFO][com.lxp.tool.log.RunnabeTestHelper][pool-1-thread-1][lambda$getRunnable$0][16] -> This is runnable.
2018-10-27 23:09:22 [INFO][com.lxp.tool.log.LogAndCatchExceptionRunnableTest][main][testRun][48] -> test finish

org.slf4j.MDC类提供了一个极好的解决方案,它可以为各个线程设置独有的上下文,当有必要时也可以把主线程的上下文复制给子线程,此时子线程可以拥有主线程+子线程的信息,在子线程退出前恢复到主线程上下文,如此一来,日志信息可以极大地便利定位问题,org.slf4j.MDC类在线程上下文切换上的应用记录本文的目的之一。
另一个则是过去一直被自己忽略的多线程时退出的问题,任务需要多线程执行有两种可能场景

  • 多个任务互相独立,某个任务失败并不应该影响其它的任务继续执行
  • 多个子任务组成一个完整的主任务,若某个子任务失败它应该直接退出,不需要等所有子任务完成

3. org.slf4j.MDC类在线程上下文切换时的应用

3.1 实现包装线程

  • AbstractLogWrapper
public class AbstractLogWrapper<T> {private final T job;private final Map<?, ?> context;public AbstractLogWrapper(T t) {this.job = t;this.context = MDC.getCopyOfContextMap();}public void setLogContext() {if (this.context != null) {MDC.setContextMap(this.context);}}public void clearLogContext() {MDC.clear();}public T getJob() {return this.job;}
}
  • LogRunnable
public class LogRunnable extends AbstractLogWrapper<Runnable> implements Runnable {public LogRunnable(Runnable runnable) {super(runnable);}@Overridepublic void run() {// 把主线程上下文复到子线程this.setLogContext();try {getJob().run();} finally {// 恢复主线程上下文this.clearLogContext();}}
}
  • LogAndCatchExceptionRunnable
public class LogAndCatchExceptionRunnable extends AbstractLogWrapper<Runnable> implements Runnable {private static final Logger LOGGER = LoggerFactory.getLogger(LogAndCatchExceptionRunnable.class);public LogAndCatchExceptionRunnable(Runnable runnable) {super(runnable);}@Overridepublic void run() {// 把主线程上下文复到子线程this.setLogContext();try {getJob().run();} catch (Exception e) { // Catch所有异常阻止其继续传播LOGGER.error(e.getMessage(), e);} finally {// 恢复主线程上下文this.clearLogContext();}}
}

3.2 配置%X输出当前线程相关联的NDC

<?xml version="1.0" encoding="UTF-8"?>
<configuration><appender name="stdot" class="ch.qos.logback.core.ConsoleAppender"><layout class="ch.qos.logback.classic.PatternLayout"><pattern>%d{yyyy-MM-dd HH:mm:ss} [%p][%c][%t][%M][%L] %replace(Test_Method=%X{method} runn-able=%X{runn_able}){'.+=( |$)', ''} -> %m%n</pattern></layout></appender><root level="debug"><appender-ref ref="stdot"/></root>
</configuration>

3.3 配置线程相关信息并测试

class RunnabeTestHelper {private static final Logger LOGGER = LoggerFactory.getLogger(RunnabeTestHelper.class);private static final String RUNNABLE = "runn_able";static Runnable getRunnable() {return () -> {MDC.put(RUNNABLE, String.valueOf(System.currentTimeMillis()));LOGGER.info("This is runnable.");};}
}
  • 测试方法
    @Testpublic void testRun() {try {MDC.put("method", "testRun");LOGGER.info("test start");LogAndCatchExceptionRunnable logRunnable = spy(new LogAndCatchExceptionRunnable(RunnabeTestHelper.getRunnable()));Set<String> set = new HashSet<>();doAnswer(invocation -> set.add(invocation.getMethod().getName())).when(logRunnable).setLogContext();doAnswer(invocation -> set.add(invocation.getMethod().getName())).when(logRunnable).clearLogContext();List<CompletableFuture<Void>> futures = IntStream.rangeClosed(0, 4).mapToObj(index -> CompletableFuture.runAsync(logRunnable, executorService)).collect(Collectors.toList());futures.forEach(CompletableFuture::join);assertEquals("[setLogContext, clearLogContext]", set.toString());LOGGER.info("test finish");} finally {MDC.clear();}}
  • 测试结果
2018-11-01 01:08:04 [INFO][com.lxp.tool.log.LogRunnableTest][main][testRun][41]  -> test start
2018-11-01 01:08:05 [INFO][com.lxp.tool.log.RunnabeTestHelper][pool-1-thread-1][lambda$getRunnable$0][16] Test_Method=testRun runn-able=1541005685003 -> This is runnable.
2018-11-01 01:08:05 [INFO][com.lxp.tool.log.RunnabeTestHelper][pool-1-thread-1][lambda$getRunnable$0][16] Test_Method=testRun runn-able=1541005685004 -> This is runnable.
2018-11-01 01:08:05 [INFO][com.lxp.tool.log.RunnabeTestHelper][pool-1-thread-1][lambda$getRunnable$0][16] Test_Method=testRun runn-able=1541005685004 -> This is runnable.
2018-11-01 01:08:05 [INFO][com.lxp.tool.log.RunnabeTestHelper][pool-1-thread-2][lambda$getRunnable$0][16] Test_Method=testRun runn-able=1541005685003 -> This is runnable.
2018-11-01 01:08:05 [INFO][com.lxp.tool.log.RunnabeTestHelper][pool-1-thread-2][lambda$getRunnable$0][16] Test_Method=testRun runn-able=1541005685005 -> This is runnable.
2018-11-01 01:08:05 [INFO][com.lxp.tool.log.LogRunnableTest][main][testRun][50]  -> test finish

4. 多线程执行子线程出现异常时的处理

class RunnabeTestHelper {private static final Logger LOGGER = LoggerFactory.getLogger(RunnabeTestHelper.class);static Runnable getRunnable(AtomicInteger counter) {return () -> {try {Thread.sleep(1000);} catch (InterruptedException e) {LOGGER.error(e.getMessage(), e);}if (counter.incrementAndGet() == 2) {throw new NullPointerException();}LOGGER.info("This is {} runnable.", counter.get());};}static Runnable getRunnableWithCatchException(AtomicInteger counter) {return () -> {try {Thread.sleep(1000);if (counter.incrementAndGet() == 2) {throw new NullPointerException();}LOGGER.info("This is {} runnable.", counter.get());} catch (Exception e) {LOGGER.error("error", e);}};}
}

4.1 选择一:放充执行未执行的其它子线程

  • 调用LogRunnable,允许子线程的异常继续传播
    @Testpublic void testRunnableWithoutCatchException() {Logger logger = Mockito.mock(Logger.class);AtomicInteger counter = new AtomicInteger(0);List<CompletableFuture<Void>> futures = IntStream.rangeClosed(0, 4).mapToObj(index -> CompletableFuture.runAsync(new LogRunnable(RunnabeTestHelper.getRunnable(counter)), executorService)).collect(Collectors.toList());try {futures.forEach(CompletableFuture::join);} catch (Exception e) {logger.error(e.getMessage(), e);}// 由于子线程的异常导致主线程退出,并不是所有任务都得到执行机会assertEquals(2, counter.get());verify(logger, Mockito.times(1)).error(anyString(), any(Throwable.class));}

4.2 选择二:执行完所有无异常的子线程

  • 调用LogRunnable,在线程内部阻止异常扩散
    @Testpublic void testRunnableWithCatchException() {AtomicInteger counter = new AtomicInteger(0);List<CompletableFuture<Void>> futures = IntStream.rangeClosed(0, 4).mapToObj(index -> CompletableFuture.runAsync(new LogRunnable(RunnabeTestHelper.getRunnableWithCatchException(counter)), executorService)).collect(Collectors.toList());futures.forEach(CompletableFuture::join);// 由于子线程的异常被阻止,所有线程都得到执行机会assertEquals(5, counter.get());}
  • 调用LogAndCatchExceptionRunnable,在包装类阻止异常扩散
    @Testpublic void testRunnableWithoutCatchException() {AtomicInteger counter = new AtomicInteger(0);List<CompletableFuture<Void>> futures = IntStream.rangeClosed(0, 4).mapToObj(index -> CompletableFuture.runAsync(new LogAndCatchExceptionRunnable(RunnabeTestHelper.getRunnable(counter)), executorService)).collect(Collectors.toList());futures.forEach(CompletableFuture::join);// 由于子线程的异常被阻止,所有线程都得到执行机会assertEquals(5, counter.get());}@Testpublic void testRunnableWithCatchException() {AtomicInteger counter = new AtomicInteger(0);List<CompletableFuture<Void>> futures = IntStream.rangeClosed(0, 4).mapToObj(index -> CompletableFuture.runAsync(new LogAndCatchExceptionRunnable(RunnabeTestHelper.getRunnableWithCatchException(counter)), executorService)).collect(Collectors.toList());futures.forEach(CompletableFuture::join);// 由于子线程的异常被阻止,所有线程都得到执行机会assertEquals(5, counter.get());}

转载于:https://www.cnblogs.com/hiver/p/9863883.html

使用CompletableFuture+ExecutorService+Logback的多线程测试相关推荐

  1. 匿名函数应用-多线程测试代码

    多线程测试代码,不用单独再写一个类,比较方面而已. public class MainThread { static int a=0; for (int x = 0;x<100;x++){ Th ...

  2. 【OpenCV】cv::VideoCapture 多线程测试

    cv::VideoCapture多线程测试结果: 在多线程中使用抓取摄像头视频帧时线程安全的,但是,多个线程会共用摄像头的总帧率. 比如,我用两个线程测试30帧的摄像头,每个线程差多都是15帧.

  3. Junit如何进行多线程测试

    Junit和许多开源软件项目集成在一起,但是Junit执行多线程的单元测试有一些问题.这篇文章介绍Junit的一个扩展类库―――GroboUtils,这个类库被设计为来解决这些问题,并且使在Junit ...

  4. cpu线程测试软件,CPU多线程测试:wPrime/国际象棋

    CPU多线程测试:wPrime/国际象棋 CPU多线程测试:wPrime/国际象棋 wPrime是一款通过算质数来测试计算机运算能力等的软件(特别是并行能力),但与Super Pi只能支持单线程不同的 ...

  5. Junit单元测试不支持多线程测试问题全解析

    一.背景 今天@段段提出了一个很好的问题,她发现单元测试时如果开多个线程,主线程运行结束就结束了,并不会等待子线程结束. 如果用main方法就没问题,技术群里展开了激烈的讨论. 本文将"复现 ...

  6. 【Python3.6爬虫学习记录】(十一)使用代理IP及用多线程测试IP可用性--刷访问量

    前言:本来准备写一个刷空间留言的脚本,然而kb TX,无限循环空间验证码.上午还傻x的学验证码识别,后来才发现根本发不了留言,即使填的是对的,仍然继续弹出.无奈,睡了一觉,开始搞新玩意–代理IP!其实 ...

  7. c语言多实力测试,C语言 多线程测试

    1.CreateThread 在主线程的基础上创建一个新线程 2.WaitForMultipleObjects 主线程等待子线程 3.CloseHandle 关闭线程 // testThread.cp ...

  8. Java 多线程 测试

    Java 多线程 测试 1 Callable 2 Runnable 3 Thread 1 Callable package com.xu.thread;import java.util.concurr ...

  9. java多线程测试框架(含入参和返回值)

    最近要对一个webservice接口做测试,需测试高并发接口是否会以异常数据返回,编写了如下demo,有类似需求的可以参考下. 注意事项: 1,线程使用了callable接口形式,call相对runa ...

最新文章

  1. 【SQL进阶】03.执行计划之旅1 - 初探
  2. Linux下实现USB口的热插拔
  3. SAP Spartacus里和focus相关的directive之间的继承关系
  4. C/C++深入剖析指针机制与内存动态管理
  5. android 图片传递,如何使用包在Android活动之间传递图像(位图)?
  6. 使用Forms验证存储用户自定义信息
  7. 自如:全额承担“望京跑路二房东”受害客户损失,预计约500余万元
  8. 【BZOJ3328】PYXFIB 数论+矩阵乘法
  9. 零基础学python-零基础学习Python需要多久?多少钱?
  10. Xmind 常用快捷键列表(官方推荐)
  11. 用户与系统(unix)
  12. 华为防火墙配置IPSEC实现二个站点间网络互通 隧道模式 web配置(二)
  13. 二分法和黄金分割法的区别和联系,附Python代码
  14. Java字母加数字组合比较大小
  15. Irrlicht Engine 相关信息——一些常见问题和解答
  16. Rb-tree中删除元素后树形调整函数_Rb_tree_rebalance_for_erase
  17. RK3568平台开发系列讲解(安卓适配篇)Android11 预安装应用功能
  18. 5.1 傅里叶展开,傅里叶级数推导
  19. 第一性原理·非线性成长·人生模式
  20. [ZT]JavaScript+div实现模态对话框[修正版]

热门文章

  1. WCF 项目应用连载[9] - 契约中的委托 事件参数处理
  2. 一建课件下载2019年一级建造师报考流程图,报考步骤一目了然
  3. SQL基础语法练习题(1)
  4. python编程培训多少钱-编程培训多少钱,python编程培训多少钱
  5. 6 客户端认证方式 之 client_secret_basic client_secret_post
  6. 盛世昊通打造线上线下融合的百业联盟商业生态
  7. 一个P2P未跑路平台老板的自白
  8. 媒体 | 冒志鸿:理想nine percent最难寻
  9. 一个简单的学籍信息管理系统,基于PHP和Bootstrap的实现
  10. 软件工程实训有必要吗_软件工程专业实训心得体会