写点什么

父子线程共用线程池

作者:soap said
  • 2023-08-07
    北京
  • 本文字数:4758 字

    阅读完需:约 16 分钟

1、背景

在一个老项目中发现有大量多线程使用了同一线程池,后续迭代中需要多并发调用项目接口,经常出现接口返回空列表,经过排查,发现线程池队列满了,任务被拒绝,导致异常;于是创建独立线程池用于该接口查询,问题得到“解决”。但新功能上线后,发现线上环境也会偶发返回空列表的情况,因此进一步排查,最终发现是父子线程共用线程池导致假死现象。


2、复现

public static final ExecutorService executorService =             new ThreadPoolExecutor(3,                    3,                    0,                    TimeUnit.MINUTES,                    new LinkedBlockingQueue<>(100),                    threadFactory,                    new ThreadPoolExecutor.AbortPolicy());


@Test public void m1() { List<Integer> list = Lists.newArrayList(1, 2, 3, 4, 5); List<CompletableFuture<Integer>> allFutures = Lists.newArrayList(); for (Integer i : list) { allFutures.add(CompletableFuture.supplyAsync(() -> { m2(); log.info("xxxx:{}",i); return i; }, executorService)); }
CompletableFuture<Object> completableFuture = CompletableFuture.anyOf(allFutures.toArray(new CompletableFuture[0])); completableFuture.join(); }
public void m2() { List<Integer> list = Lists.newArrayList(1, 2, 3, 4, 5); List<CompletableFuture<Integer>> allFutures = Lists.newArrayList(); for (Integer i : list) { allFutures.add(CompletableFuture.supplyAsync(() -> i, executorService)); }
CompletableFuture<Object> completableFuture = CompletableFuture.anyOf(allFutures.toArray(new CompletableFuture[0])); completableFuture.join(); }
复制代码

3、分析

3.1 找到对应线程

 jps -v        42081 RemoteMavenServer36 -Djava.awt.headless=true -Dmaven.defaultProjectBuilder.disableGlobalModelCache=true -Didea.version=2022.2.3 -Didea.maven.embedder.version=3.8.1 -Xmx768m -Dmaven.ext.class.path=/Applications/IntelliJ IDEA.app/Contents/plugins/maven/lib/maven-event-listener.jar -Dfile.encoding=UTF-86040  -Djna.library.path=/Applications/JetBrains Toolbox.app/Contents/MacOS -Djna.boot.library.path=/Applications/JetBrains Toolbox.app/Contents/MacOS -Dskiko.library.path=/Applications/JetBrains Toolbox.app/Contents/MacOS -Duser.dir=/Applications/JetBrains Toolbox.app/Contents/MacOS -Dskiko.metal.gpu.priority=integrated -Xmx160m -Xms8m -Xss384k -XX:+UnlockExperimentalVMOptions -XX:MetaspaceSize=16m -XX:MinMetaspaceFreeRatio=10 -XX:MaxMetaspaceFreeRatio=10 -XX:+UseCompressedOops -XX:+UseCompressedClassPointers -XX:+UseSerialGC -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=10 -XX:-ShrinkHeapInSteps --add-opens=java.desktop/sun.awt=ALL-UNNAMED --add-opens=java.desktop/sun.awt.resources=ALL-UNNAMED --add-opens=java.desktop/javax.swing=ALL-UNNAMED --add-opens=java.desktop/com.jetbrains.desktop=ALL-UNNAMED jna.nounpack=true jna.nosys=true jna.noclasspath=true vfprintf exit abort -DTOOLBOX_VERSION=1.28.1.15219 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Users/soapy/Library/Logs/JetBrains/Toolbox/toolbox-oom-6040.hpr42025  -Xms128m -Xmx2048m -XX:ReservedCodeCacheSize=512m -XX:+IgnoreUnrecognizedVMOptions -XX:+UseG1GC -XX:SoftRefLRUPolicyMSPerMB=50 -XX:CICompilerCount=2 -XX:+HeapDumpOnOutOfMemoryError -XX:-OmitStackTraceInFastThrow -ea -Dsun.io.useCanonCaches=false -Djdk.http.auth.tunneling.disabledSchemes="" -Djdk.attach.allowAttachSelf=true -Djdk.module.illegalAccess.silent=true -Dkotlinx.coroutines.debug=off -XX:ErrorFile=/Users/soapy/java_error_in_idea_%p.log -XX:HeapDumpPath=/Users/soapy/java_error_in_idea.hprof --add-opens=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED --add-opens=java.base/jdk.internal.org.objectweb.asm.tree=ALL-UNNAMED -javaagent:/Users/soapy/Downloads/jetbra/ja-netfilter.jar=jetbrains -Djb.vmOptionsFile=/Users/soapy/Downloads/jetbra/vmoptions/idea.vmoptions --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.nio=ALL-UNNAMED --add-opens=java.bas45932 Launcher -Xmx700m -Djava.awt.headless=true -Djava.endorsed.dirs="" -Dpreload.project.path=/Users/soapy/IdeaProjects/hbdata-appflow-ocean-analyze -Dpreload.config.path=/Users/soapy/Library/Application Support/JetBrains/IntelliJIdea2022.2/options -Dexternal.project.config=/Users/soapy/Library/Caches/JetBrains/IntelliJIdea2022.2/external_build_system/hbdata-appflow-ocean-analyze.3c9f3abd -Dcompile.parallel=false -Drebuild.on.dependency.change=true -Didea.IntToIntBtree.page.size=32768 -Djdt.compiler.useSingleThread=true -Daether.connector.resumeDownloads=false -Dio.netty.initialSeedUniquifier=2308441280104614539 -Djps.track.ap.dependencies=false -Dfile.encoding=UTF-8 -Duser.language=zh -Duser.country=CN -Didea.paths.selector=IntelliJIdea2022.2 -Djava.net.preferIPv4Stack=true -Didea.home.path=/Applications/IntelliJ IDEA.app/Contents -Didea.config.path=/Users/soapy/Library/Application Support/JetBrains/IntelliJIdea2022.2 -Didea.plugins.path=/Users/soapy/Library/Application Support/JetBrains/IntelliJIdea2022.2/plugins -Djps.33485  -XX:+IgnoreUnrecognizedVMOptions --add-modules=ALL-SYSTEM --add-opens=java.base/java.nio=ALL-UNNAMED -Dosgi.requiredJavaVersion=11 -Xms64m -Xmx1024m -XstartOnFirstThread45933 JUnitStarter -ea -Didea.test.cyclic.buffer.size=1048576 -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=51969:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8
复制代码


3.2 根据线程堆栈,找到问题代码

jstack 45933"main" #1 prio=5 os_prio=31 tid=0x00007f9658809800 nid=0x1b03 waiting on condition [0x0000700004b7c000]   java.lang.Thread.State: WAITING (parking)        at sun.misc.Unsafe.park(Native Method)        - parking to wait for  <0x00000006c0612778> (a java.util.concurrent.CompletableFuture$Signaller)        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)        at java.util.concurrent.CompletableFuture$Signaller.block(CompletableFuture.java:1693)        at java.util.concurrent.ForkJoinPool.managedBlock(ForkJoinPool.java:3323)        at java.util.concurrent.CompletableFuture.waitingGet(CompletableFuture.java:1729)        at java.util.concurrent.CompletableFuture.join(CompletableFuture.java:1934)        at com.sankuai.hbdata.flow.ocean.ThreadPoolTest.m1(ThreadPoolTest.java:50)        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)        at java.lang.reflect.Method.invoke(Method.java:498)        at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)        at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)        at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)        at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)        at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)        at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)        at org.junit.runners.ParentRunner.run(ParentRunner.java:363)        at org.junit.runner.JUnitCore.run(JUnitCore.java:137)        at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)        at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)        at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)        at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)        at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)        at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
复制代码


3.3 原因分析

m1 调用 m2,都从同一个线程池中获取线程,当 m2 执行时,如果线程池不够,就只能进入队列中,而此时 m1 的 join 方法一直获取不到 m2 的结果,所以一直占用线程不释放处于 waiting 状态。

4.推荐用法

避免踩坑,请使用 线程池开发红线规范

第一,高优使用动态线程池。

dynamictp 美团基于到店开源

hippo4j


第二,获取异步执行结果,必须设置超时参数。

【强制】禁止使用无界队列。

【强制】使用有界队列时,严禁 size 设置为 0。

【强制】使用 future.get 时,一定要设置超时时间 eg:future.get(123, TimeUnit.MILLISECONDS), 尽量避免使用 join()。在很多场景下,设置超时时间能帮助我们避免或者缓解很多故障,比如父子线程。

【强制】不建议不同场景复用一个线程池。如果存在复用线程池,注意一定要查看是否属于父子线程(但是随着时间业务迭代可能会再次忽略,所以强烈建议不要复用线程池)。

【建议】使用 CompletableFuture.supplyAsync 时,建议自定义线程池,不用采用默认线程池(commonParallelism <=1 每次都创建新线程,commonParallelism>1 使用 commonPool)。

【建议】对于 C 端/实时场景,不建议使用有界队列,推荐使用 SynchronousQueue,达到使用 failfast。

【建议】不要使用局部变量的线程池。如果不得不使用局部变量的线程池,一定要记得在 finally 中调用 shutdown。

【建议】拒绝策略推荐使用 AbortPolicy/CallerRunsPolicy 或者自定义。

发布于: 刚刚阅读数: 5
用户头像

soap said

关注

还未添加个人签名 2020-08-25 加入

表达自己,分享闪念,共同进步

评论

发布
暂无评论
父子线程共用线程池_soap said_InfoQ写作社区