细数ThreadLocal三大坑,内存泄露仅是小儿科( 三 )

可以看到 , 我们初始化线程池的时候指定如果线程池满 , 则新提交的任务转为串行执行 , 那我们之前的写法就会有问题了 , 串行执行的时候调用ContextHolder.remove;会将主线程的上下文也清理 , 即使后面线程池继续并行工作 , 传给子线程的上下文也已经是null了 , 而且这样的问题很难在预发测试的时候发现 。
如果ThreadLocal碰到并行流 , 也会有很多有意思的事情发生 , 比如有下面的代码:
class ParallelProcessor<T> { public void process(List<T> dataList) { // 先校验参数 , 篇幅限制先省略不写 dataList.parallelStream.forEach(entry -> { doIt; }); } private void doIt { String session = ContextHolder.get; // do something }} 这段代码很容易在线下测试的过程中发现不能按照预期工作 , 因为并行流底层的实现也是一个ForkJoin线程池 , 既然是线程池 , 那ContextHolder.get可能取出来的就是一个null 。 我们顺着这个思路把代码再改一下:
class ParallelProcessor<T> { private String session; public ParallelProcessor(String session) { this.session = session; } public void process(List<T> dataList) { // 先校验参数 , 篇幅限制先省略不写 dataList.parallelStream.forEach(entry -> { try { ContextHolder.set(session); // 业务处理 doIt; } catch (Exception e) { // log it } finally { ContextHolder.remove; } }); } private void doIt { String session = ContextHolder.get; // do something }} 【细数ThreadLocal三大坑,内存泄露仅是小儿科】修改完后的这段代码可以工作吗?如果运气好 , 你会发现这样改又有问题 , 运气不好 , 这段代码在线下运行良好 , 这段代码就顺利上线了 。 不久你就会发现系统中会有一些其他很诡异的bug 。 原因在于并行流的设计比较特殊 , 父线程也有可能参与到并行流线程池的调度 , 那如果上面的process方法被父线程执行 , 那么父线程的上下文会被清理 。 导致后续拷贝到子线程的上下文都为null , 同样产生丢失上下文的问题 。