多好听 發表於 2025-10-2 21:52:00

Java并发编程(5)

<hr>
<h1 style="text-align: center">线程池</h1>
<h1>1、什么是线程池?</h1>
<ul>
<li>管理线程,避免增加创建线程和销毁线程的资源消耗:线程也是一个对象,创建一个对象要类加载,销毁一个对象要走GC垃圾回收流程,都是有资源开销的。</li>
<li>提高响应速度:对比普通的做法,是重新创建一个线程执行,要慢很多。</li>
<li>重复利用:线程用完再放回池子,可以达到重复利用的效果,节省资源。</li>
</ul>
<h1>2、说说工作中线程池的应用</h1>
<p>&nbsp;  可以讲在拼团中怎么用的。</p>
<h1>3、说一下线程池的工作流程</h1>
<ul>
<li>&nbsp;线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的(因为可以选择)。</li>
<li>当调用excute()方法添加一个任务时,线程池会做如下判断:
<ul>
<li>若正在运行的线程数量小于corePoolSize,则马上创建线程运行这个任务。</li>
<li>若正在运行的线程数量大于或等于corePoolSize,则将这个任务放入队列。</li>
<li>若这时队列满了,而且正在运行的线程数量小于maximumPoolSize,那么还是要创建非核心线程立刻执行这个任务。</li>
<li>若队列满了,且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会根据拒绝策略来应对处理。</li>
</ul>
</li>
<li>当一个线程完成任务时,它会从队列中取下一个任务来执行。</li>
<li>当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,若当前正在运行的线程数大于corePoolSize,那么这个线程会被停掉。所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。</li>
</ul>
<blockquote>
<p>为什么不直接填满到maximyumPoolSize:</p>
<ul>
<li>内存占用大、上下文切换频繁消耗CPU、同步开销大。</li>
<li>动态扩展的方案只在高峰期临时创建更多线程,是一种“按需分配”的原则:先用少量核心线程处理常规负载,队列缓冲短期突发请求,实在处理不过来才会额外创建线程,空闲时回收多余线程。是为了在响应速度和资源效率之间取得最佳平衡。</li>
</ul>
</blockquote>
<h1>4、线程池主要参数有哪些?</h1>
<ul>
<li>corePoolSize:初始化线程池中核心线程数,当线程池中线程数小于corePoolSize时,系统默认是添加一个任务才从创建一个线程。</li>
<li>maximumPoolSize:表示允许的最大线程数 = 非核心线程数+ 核心线程数。当工作队列也满了,但线程池中总线程数&lt;maximumPoolSize时就会再次创建新的线程。</li>
<li>keepAliveTime:非核心线程闲置下来不干活最多存活时间。</li>
<li>unit:非核心线程保持存活的时间的单位。
<ul>
<li>
<div>TimeUnit.DAYS; 天</div>
<div>TimeUnit.HOURS; ⼩时</div>
<div>TimeUnit.MINUTES; 分钟</div>
<div>TimeUnit.SECONDS; 秒</div>
<div>TimeUnit.MILLISECONDS; 毫秒</div>
<div>TimeUnit.MICROSECONDS; 微秒</div>
<div>TimeUnit.NANOSECONDS; 纳秒</div>
</li>
</ul>
</li>
<li>workQueue:线程池等待队列</li>
</ul>
<blockquote><ol>
<li>ArrayBlockingQueue:<strong>有界队列</strong>是一个用数组实现的有界阻塞队列,按FIFO排序量。</li>
<li>LinkedBlockingQueue:基于<strong>链表结构</strong>的阻塞队列,按FIFO排序任务,容量可以选择进行设置,不设置的话就是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE,吞吐量通常要高于ArrayBlockingQueue;newFixedThreadPool线程池用了这个队列。</li>
<li>DelayQueue:延迟队列是一个<strong>任务定时</strong>周期的执行的队列。单纯的DealyDueue不保证相同延迟任务的FIFO,但ScheduledThreadPoolExecutor通过添加序列号的方式,确保了FIFO顺序。(任务1还有2秒执行,任务2还有3秒执行,那么任务1排在任务2前面)
<ul>
<li><code>getDelay()</code>返回距离触发还有多久</li>
<li>当<code>getDelay() &lt;= 0</code>时,任务才可被取出</li>
<li data-spm-anchor-id="5176.28103460.0.i11.2f2e2e311GVuEr">队列按触发时间排序,最早该执行的任务在队首</li>
</ul>
</li>
<li>priorityBlockingQueue:是具有优先级的<strong>无界阻塞</strong>队列。</li>
<li>SynchronousQueue:是一个<strong>不存储元素</strong>的阻塞队列,每个插入操作必须等另一个线程调用移除操作,否则插入操作一直处于阻塞,吞吐量通常高于
<div>LinkedBlockingQuene(因为是生产者直接交给消费者,无缓冲,适合突发性、短时任务),<strong>newCachedThreadPool</strong>线程池使⽤了这个队列。</div>
</li>
</ol></blockquote>
<ul>
<li>threadFactory:创建一个新线程时用的工厂,可以用来设定线程名、是否为daemon线程等。</li>
</ul>
<p>&nbsp;</p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">// 1. 默认工厂(抽象但可用)
ThreadFactory defaultFactory = Executors.defaultThreadFactory();

// 2. 自定义工厂 - 可以完全控制线程创建过程
ThreadFactory customFactory = new ThreadFactory() {
    private final AtomicInteger threadNumber = new AtomicInteger(1);
   
    @Override
    public Thread newThread(Runnable r) {
      Thread t = new Thread(r);
      t.setName("my-pool-worker-" + threadNumber.getAndIncrement());
      t.setDaemon(false); // 设置为非守护线程
      t.setPriority(Thread.NORM_PRIORITY); // 设置优先级
      return t;
    }
};

// 3. 在线程池中使用
ThreadPoolExecutor pool = new ThreadPoolExecutor(
    CORE_POOL_SIZE,
    MAXIMUM_POOL_SIZE,
    60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue&lt;Runnable&gt;(),
    customFactory, // 这里传入自定义工厂
    new ThreadPoolExecutor.CallerRunsPolicy()
);</pre>
</div>
<ul>
<li>handler:拒绝策略。</li>
</ul>
<blockquote><ol>
<li>AbortPolicy:直接抛出异常,默认使用此策略</li>
<li>CallerFunsPolicy:用调用者所在的线程执行任务</li>
<li>DIscartOldestPolicy:丢弃阻塞队列里最老的任务,也就是队列里最靠前的任务</li>
<li>DiscardPolicy:当前任务直接丢弃</li>
<li>自定义:实现RejectExecutionHandler接口即可。</li>
</ol></blockquote>
<h1>5、线程池提交excute和submit有什么区别</h1>
<h2 data-start="185" data-end="201">(1) execute()</h2>
<ul data-start="202" data-end="377">
<li data-start="202" data-end="241">
<p data-start="204" data-end="241">用法:<code data-start="207" data-end="239">void execute(Runnable command)</code></p>
</li>
<li data-start="242" data-end="377">
<p data-start="244" data-end="249">特点:</p>
<ul data-start="252" data-end="377">
<li data-start="252" data-end="277">
<p data-start="254" data-end="277">只能提交 <strong data-start="259" data-end="274">Runnable 任务</strong>;</p>
</li>
<li data-start="280" data-end="294">
<p data-start="282" data-end="294"><strong data-start="282" data-end="291">没有返回值</strong>;</p>
</li>
<li data-start="297" data-end="377">
<p data-start="299" data-end="377">如果任务抛出异常,异常会直接抛到线程中,最终由线程的 <strong data-start="326" data-end="354">UncaughtExceptionHandler</strong> 或 <code data-start="357" data-end="371">afterExecute</code> 捕获</p>
</li>
</ul>
</li>
</ul>
<div class="cnblogs_code">
<pre>(1<span style="color: rgba(0, 0, 0, 1)">) execute()

用法:</span><span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> execute(Runnable command)

特点:

只能提交 Runnable 任务;

没有返回值;

如果任务抛出异常,异常会直接抛到线程中,最终由线程的 UncaughtExceptionHandler 或 afterExecute 捕获。</span></pre>
</div>
<h2 data-start="550" data-end="565">(2) submit()</h2>
<ul data-start="566" data-end="823">
<li data-start="566" data-end="660">
<p data-start="568" data-end="573">用法:</p>
<ul data-start="576" data-end="660">
<li data-start="576" data-end="613">
<p data-start="578" data-end="613"><code data-start="578" data-end="611">Future&lt;?&gt; submit(Runnable task)</code></p>
</li>
<li data-start="616" data-end="660">
<p data-start="618" data-end="660"><code data-start="618" data-end="658">&lt;T&gt; Future&lt;T&gt; submit(Callable&lt;T&gt; task)</code></p>
</li>
</ul>
</li>
<li data-start="661" data-end="823">
<p data-start="663" data-end="668">特点:</p>
<ul data-start="671" data-end="823">
<li data-start="671" data-end="707">
<p data-start="673" data-end="707">可以提交 <strong data-start="678" data-end="701">Runnable 或 Callable</strong> 任务;</p>
</li>
<li data-start="710" data-end="748">
<p data-start="712" data-end="748">返回 <strong data-start="715" data-end="725">Future</strong> 对象,可以获取结果、取消任务、捕获异常;</p>
</li>
<li data-start="751" data-end="823">
<p data-start="753" data-end="823">如果任务抛出异常,异常会被封装在 <code data-start="770" data-end="778">Future</code> 中,只有调用 <code data-start="786" data-end="793">get()</code> 时才会抛出 <code data-start="800" data-end="820">ExecutionException</code>。</p>
</li>
</ul>
</li>
</ul>
<div class="cnblogs_code">
<pre>(2<span style="color: rgba(0, 0, 0, 1)">) submit()

用法:

Future</span>&lt;?&gt;<span style="color: rgba(0, 0, 0, 1)"> submit(Runnable task)

</span>&lt;T&gt; Future&lt;T&gt; submit(Callable&lt;T&gt;<span style="color: rgba(0, 0, 0, 1)"> task)

特点:

可以提交 Runnable 或 Callable 任务;

返回 Future 对象,可以获取结果、取消任务、捕获异常;

如果任务抛出异常,异常会被封装在 Future 中,只有调用 get() 时才会抛出 ExecutionException。</span></pre>
</div>
<h1>6、线程池怎么关闭</h1>
<p data-start="66" data-end="86">(1) <strong data-start="70" data-end="84">shutdown()</strong></p>
<ul data-start="87" data-end="191">
<li data-start="87" data-end="129">
<p data-start="89" data-end="129">行为:停止接收新任务;已提交的任务(正在执行的 + 队列中等待的)会执行完。</p>
</li>
<li data-start="130" data-end="151">
<p data-start="132" data-end="151">特点:<strong data-start="135" data-end="143">平滑关闭</strong>,常用方式。</p>
</li>
<li data-start="152" data-end="191">
<p data-start="154" data-end="191">注意:调用后线程池状态会变为 <strong data-start="169" data-end="181">SHUTDOWN</strong>,不会立刻终止。</p>
</li>
</ul>
<p data-start="193" data-end="216">(2) <strong data-start="197" data-end="214">shutdownNow()</strong></p>
<ul data-start="217" data-end="338">
<li data-start="217" data-end="256">
<p data-start="219" data-end="256">行为:停止接收新任务,清空队列中的等待任务,并尝试中断正在执行的任务。</p>
</li>
<li data-start="257" data-end="274">
<p data-start="259" data-end="274">返回值:未执行的任务列表。</p>
</li>
<li data-start="275" data-end="307">
<p data-start="277" data-end="307">特点:<strong data-start="280" data-end="288">强制关闭</strong>,风险较大(可能造成数据不一致)。</p>
</li>
<li data-start="308" data-end="338">
<p data-start="310" data-end="338">注意:线程是否真正中断,取决于任务代码是否响应中断。</p>
</li>
</ul>
<p data-start="340" data-end="381">(3) <strong data-start="344" data-end="379">awaitTermination(timeout, unit)</strong></p>
<ul data-start="382" data-end="611">
<li data-start="382" data-end="508">
<p data-start="384" data-end="389">用法:</p>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="sticky top-9">&nbsp;</div>
<div class="overflow-y-auto p-4" dir="ltr"><code class="whitespace-pre! language-java">pool.shutdown(); <span class="hljs-keyword">if (!pool.awaitTermination(<span class="hljs-number">60, TimeUnit.SECONDS)) { pool.shutdownNow(); } </span></span></code></div>
</div>
</li>
<li data-start="509" data-end="582">
<p data-start="511" data-end="582">作用:在调用 <code data-start="518" data-end="530">shutdown()</code> 后等待一段时间,如果线程池没有在指定时间内终止,再调用 <code data-start="559" data-end="574">shutdownNow()</code> 强制关闭。</p>
</li>
<li data-start="583" data-end="611">
<p data-start="585" data-end="611">特点:常用于“优雅关闭”+“兜底强制关闭”组合。</p>
</li>
</ul>
<p data-start="613" data-end="652">(4) <strong data-start="617" data-end="650">isShutdown() / isTerminated()</strong></p>
<ul data-start="653" data-end="790">
<li data-start="653" data-end="717">
<p data-start="655" data-end="717"><code data-start="655" data-end="669">isShutdown()</code>:调用 <code data-start="673" data-end="685">shutdown()</code> 或 <code data-start="688" data-end="703">shutdownNow()</code> 后返回 <code data-start="708" data-end="714">true</code>。</p>
</li>
<li data-start="718" data-end="766">
<p data-start="720" data-end="766"><code data-start="720" data-end="736">isTerminated()</code>:所有任务执行结束,线程池完全终止时返回 <code data-start="757" data-end="763">true</code>。</p>
</li>
<li data-start="767" data-end="790">
<p data-start="769" data-end="790">可配合轮询检测线程池状态,做收尾逻辑。</p>
</li>
</ul>
<h1>7、线程池的线程数应该怎么配置</h1>
<p data-start="75" data-end="92">(1) <strong data-start="79" data-end="90">计算密集型任务</strong></p>
<ul data-start="93" data-end="191">
<li data-start="93" data-end="117">
<p data-start="95" data-end="117">特点:大部分时间消耗在 CPU 运算上。</p>
</li>
<li data-start="118" data-end="137">
<p data-start="120" data-end="137">配置:<code data-start="123" data-end="134">CPU核数 + 1</code>。</p>
</li>
<li data-start="138" data-end="191">
<p data-start="140" data-end="191">理由:核心数保证最大并行度,额外的 <code data-start="158" data-end="162">+1</code> 用来应对偶尔的线程挂起(页缺失、GC、上下文切换)。</p>
</li>
</ul>
<p data-start="193" data-end="211">(2) <strong data-start="197" data-end="209">IO 密集型任务</strong></p>
<ul data-start="212" data-end="314">
<li data-start="212" data-end="242">
<p data-start="214" data-end="242">特点:大部分时间等待 IO(数据库、网络、磁盘等)。</p>
</li>
<li data-start="243" data-end="280">
<p data-start="245" data-end="280">配置:<code data-start="248" data-end="259">CPU核数 * 2</code>(甚至更高,取决于 IO 等待比例)。</p>
</li>
<li data-start="281" data-end="314">
<p data-start="283" data-end="314">理由:IO 期间线程空闲,更多线程可提升 CPU 利用率。</p>
</li>
</ul>
<p data-start="316" data-end="331">(3) <strong data-start="320" data-end="329">混合型任务</strong></p>
<ul data-start="332" data-end="450">
<li data-start="332" data-end="352">
<p data-start="334" data-end="352">特点:既包含计算,又包含 IO。</p>
</li>
<li data-start="353" data-end="450">
<p data-start="355" data-end="362">配置方法:</p>
<ul data-start="365" data-end="450">
<li data-start="365" data-end="402">
<p data-start="367" data-end="402"><strong data-start="367" data-end="376">比例分解法</strong>:根据任务中 IO 和计算占比,估算最优线程数。</p>
</li>
<li data-start="405" data-end="450">
<p data-start="407" data-end="450"><strong data-start="407" data-end="414">分池法</strong>:将任务拆分为计算型和 IO 型,分别放入不同线程池,避免资源竞争。</p>
</li>
</ul>
</li>
</ul>
<p data-start="452" data-end="477">(4) <strong data-start="456" data-end="475">经验公式(IO 密集型可参考)</strong></p>
<p><span class="katex-display"><span class="katex-display"><span class="katex"><span class="katex-mathml">线程数=CPU核心数×(1+IO时间计算时间)线程数 = CPU核心数 \times (1 + \frac{IO时间}{计算时间})<span class="katex-html"><span class="base"><span class="strut"><span class="mord cjk_fallback">线程数<span class="mspace"><span class="mrel">=<span class="mspace"><span class="base"><span class="strut"><span class="mord mathnormal">CP<span class="mord mathnormal">U<span class="mord cjk_fallback">核心数<span class="mspace"><span class="mbin">×<span class="mspace"><span class="base"><span class="strut"><span class="mopen">(<span class="mord">1<span class="mspace"><span class="mbin">+<span class="mspace"><span class="base"><span class="strut"><span class="mord"><span class="mopen nulldelimiter"><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist"><span class="pstrut"><span class="mord"><span class="mord cjk_fallback">计算时间<span class="pstrut"><span class="frac-line"><span class="pstrut"><span class="mord"><span class="mord mathnormal">I<span class="mord mathnormal">O<span class="mord cjk_fallback">时间<span class="vlist-s">​<span class="vlist-r"><span class="vlist"><span class="mclose nulldelimiter"><span class="mclose">)</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></p>
<ul data-start="530" data-end="559">
<li data-start="530" data-end="559">
<p data-start="532" data-end="559">如果 IO 占比高,线程数可比 CPU 数大得多。</p>
</li>
</ul>
<h1>8、有哪几种常见的线程池</h1>
<p data-start="101" data-end="129">(1) <strong data-start="105" data-end="127">newFixedThreadPool</strong></p>
<ul data-start="130" data-end="314">
<li data-start="130" data-end="158">
<p data-start="132" data-end="158"><strong data-start="132" data-end="155">核心线程数 = 最大线程数 = 固定值</strong>。</p>
</li>
<li data-start="159" data-end="193">
<p data-start="161" data-end="193"><strong data-start="161" data-end="182">keepAliveTime = 0</strong>,线程不会被回收。</p>
</li>
<li data-start="194" data-end="235">
<p data-start="196" data-end="235"><strong data-start="196" data-end="232">阻塞队列 = LinkedBlockingQueue(无界队列)</strong>。</p>
</li>
<li data-start="236" data-end="261">
<p data-start="238" data-end="261"><strong data-start="238" data-end="244">特点</strong>:线程数固定,任务多了就排队。</p>
</li>
<li data-start="262" data-end="314">
<p data-start="264" data-end="314"><strong data-start="264" data-end="272">适用场景</strong>:适合 <strong data-start="276" data-end="289">CPU 密集型任务</strong>,线程数可设置为 <code data-start="298" data-end="307">CPU 核心数</code> 或略大。</p>
</li>
</ul>
<p data-start="321" data-end="350">(2) <strong data-start="325" data-end="348">newCachedThreadPool</strong></p>
<ul data-start="351" data-end="580">
<li data-start="351" data-end="405">
<p data-start="353" data-end="405"><strong data-start="353" data-end="366">核心线程数 = 0</strong>;最大线程数 = <code data-start="375" data-end="394">Integer.MAX_VALUE</code>(相当于无限大)。</p>
</li>
<li data-start="406" data-end="446">
<p data-start="408" data-end="446"><strong data-start="408" data-end="431">keepAliveTime = 60s</strong>,空闲线程超过时间会回收。</p>
</li>
<li data-start="447" data-end="496">
<p data-start="449" data-end="496"><strong data-start="449" data-end="493">阻塞队列 = SynchronousQueue(没有容量,任务直接交给线程执行)</strong>。</p>
</li>
<li data-start="497" data-end="529">
<p data-start="499" data-end="529"><strong data-start="499" data-end="505">特点</strong>:任务多就创建新线程,空闲一段时间自动回收。</p>
</li>
<li data-start="530" data-end="580">
<p data-start="532" data-end="580"><strong data-start="532" data-end="540">适用场景</strong>:适合 <strong data-start="544" data-end="558">大量并发的短期小任务</strong>,但要注意可能导致线程数过多,风险较大。</p>
</li>
</ul>
<p data-start="587" data-end="620">(3) <strong data-start="591" data-end="618">newSingleThreadExecutor</strong></p>
<ul data-start="621" data-end="801">
<li data-start="621" data-end="647">
<p data-start="623" data-end="647"><strong data-start="623" data-end="644">核心线程数 = 最大线程数 = 1</strong>。</p>
</li>
<li data-start="648" data-end="674">
<p data-start="650" data-end="674"><strong data-start="650" data-end="671">keepAliveTime = 0</strong>。</p>
</li>
<li data-start="675" data-end="716">
<p data-start="677" data-end="716"><strong data-start="677" data-end="713">阻塞队列 = LinkedBlockingQueue(无界队列)</strong>。</p>
</li>
<li data-start="717" data-end="755">
<p data-start="719" data-end="755"><strong data-start="719" data-end="725">特点</strong>:单线程串行执行,保证任务按照提交顺序(FIFO)执行。</p>
</li>
<li data-start="756" data-end="801">
<p data-start="758" data-end="801"><strong data-start="758" data-end="766">适用场景</strong>:适合 <strong data-start="770" data-end="782">需要顺序执行任务</strong>、或全局只需要一个后台线程的场景。</p>
</li>
</ul>
<p data-start="808" data-end="840">(4) <strong data-start="812" data-end="838">newScheduledThreadPool</strong></p>
<ul data-start="841" data-end="1003">
<li data-start="841" data-end="889">
<p data-start="843" data-end="889"><strong data-start="843" data-end="858">核心线程数 = 指定值</strong>;最大线程数 = <code data-start="867" data-end="886">Integer.MAX_VALUE</code>。</p>
</li>
<li data-start="890" data-end="916">
<p data-start="892" data-end="916"><strong data-start="892" data-end="913">keepAliveTime = 0</strong>。</p>
</li>
<li data-start="917" data-end="943">
<p data-start="919" data-end="943"><strong data-start="919" data-end="940">阻塞队列 = DelayQueue</strong>。</p>
</li>
<li data-start="944" data-end="968">
<p data-start="946" data-end="968"><strong data-start="946" data-end="952">特点</strong>:支持定时和周期性任务调度。</p>
</li>
<li data-start="969" data-end="1003">
<p data-start="971" data-end="1003"><strong data-start="971" data-end="979">适用场景</strong>:适合定时执行任务(如心跳检测、周期任务)。</p>
</li>
</ul>
<p data-start="1010" data-end="1052">(5) <strong data-start="1014" data-end="1037">newWorkStealingPool</strong>(JDK 1.8+ 新增)</p>
<ul data-start="1053" data-end="1237">
<li data-start="1053" data-end="1078">
<p data-start="1055" data-end="1078">使用 <code data-start="1058" data-end="1072">ForkJoinPool</code> 实现。</p>
</li>
<li data-start="1079" data-end="1149">
<p data-start="1081" data-end="1149"><strong data-start="1081" data-end="1100">核心线程数 = CPU 核心数</strong>(<code data-start="1101" data-end="1145">Runtime.getRuntime().availableProcessors()</code>)。</p>
</li>
<li data-start="1150" data-end="1197">
<p data-start="1152" data-end="1197"><strong data-start="1152" data-end="1158">特点</strong>:基于“工作窃取算法”,空闲线程可以从其他线程队列里偷任务执行,负载均衡。</p>
</li>
<li data-start="1198" data-end="1237">
<p data-start="1200" data-end="1237"><strong data-start="1200" data-end="1208">适用场景</strong>:适合 <strong data-start="1212" data-end="1227">大批量、小任务并行计算</strong>(如分治计算)。</p>
</li>
</ul>
<h1>9、线程池异常怎么处理?</h1>
<p data-start="92" data-end="197">  在使用线程池时,经常会遇到这样的情况:<strong data-start="111" data-end="156">线程池本身没问题,但任务(Runnable/Callable)运行过程中抛出了异常</strong>。<br data-start="157" data-end="160">
如果没有额外处理,这些异常可能会被“吞掉”,导致我们难以及时发现问题。</p>
<p data-start="199" data-end="223">本文总结了常见的几种线程池任务异常处理方式。</p>
<h2 data-start="230" data-end="252">(1) 在任务内部 try-catch</h2>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="overflow-y-auto p-4" dir="ltr">
<div class="cnblogs_code">
<pre>pool.execute(() -&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(0, 0, 255, 1)">int</span> a = 1 / 0<span style="color: rgba(0, 0, 0, 1)">;
    } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) {
      System.err.println(</span>"任务异常: " +<span style="color: rgba(0, 0, 0, 1)"> e.getMessage());
    }
});</span></pre>
</div>
</div>
</div>
<p data-start="414" data-end="475"><strong data-start="414" data-end="420">优点</strong>:简单直观,能捕获异常。<br data-start="432" data-end="435">
<strong data-start="435" data-end="441">缺点</strong>:需要每个任务都写 <code data-start="451" data-end="462">try/catch</code>,容易遗漏,不够统一。</p>
<h2 data-start="482" data-end="516">(2) <code data-start="489" data-end="499">submit()</code> + <code data-start="502" data-end="516">Future.get()</code></h2>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="overflow-y-auto p-4" dir="ltr">
<div class="cnblogs_code">
<pre>Future&lt;?&gt; future = pool.submit(() -&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    </span><span style="color: rgba(0, 0, 255, 1)">int</span> a = 1 / 0<span style="color: rgba(0, 0, 0, 1)">;
});

</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
    future.get(); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 会抛 ExecutionException</span>
} <span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) {
    System.err.println(</span>"捕获到异常: " +<span style="color: rgba(0, 0, 0, 1)"> e.getCause());
}</span></pre>
</div>
</div>
</div>
<p data-start="719" data-end="728"><strong data-start="719" data-end="725">特点</strong>:</p>
<ul data-start="729" data-end="850">
<li data-start="729" data-end="764">
<p data-start="731" data-end="764">使用 <code data-start="734" data-end="744">submit()</code> 提交任务会返回 <code data-start="753" data-end="761">Future</code>;</p>
</li>
<li data-start="765" data-end="815">
<p data-start="767" data-end="815">如果调用 <code data-start="772" data-end="779">get()</code>,会抛出 <code data-start="784" data-end="804">ExecutionException</code>,异常可以被捕获;</p>
</li>
<li data-start="816" data-end="850">
<p data-start="818" data-end="850">但如果 <strong data-start="822" data-end="837">不调用 <code data-start="828" data-end="835">get()</code></strong>,异常就会被“吞掉”。</p>
</li>
</ul>
<p data-start="852" data-end="868">适合需要拿任务返回值的场景。</p>
<h2 data-start="875" data-end="914">(3) 设置线程的 <code data-start="888" data-end="914">UncaughtExceptionHandler</code></h2>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="sticky top-9">&nbsp;
<div class="cnblogs_code">
<pre>ThreadFactory factory = r -&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    Thread t </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Thread(r);
    t.setUncaughtExceptionHandler((thread, e) </span>-&gt;<span style="color: rgba(0, 0, 0, 1)">
      System.err.println(thread.getName() </span>+ " 出错了: " +<span style="color: rgba(0, 0, 0, 1)"> e));
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> t;
};

ExecutorService pool </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ThreadPoolExecutor(
    </span>2, 4, 60L<span style="color: rgba(0, 0, 0, 1)">, TimeUnit.SECONDS,
    </span><span style="color: rgba(0, 0, 255, 1)">new</span> LinkedBlockingQueue&lt;&gt;<span style="color: rgba(0, 0, 0, 1)">(), factory
);

pool.execute(() </span>-&gt; { <span style="color: rgba(0, 0, 255, 1)">int</span> a = 1 / 0<span style="color: rgba(0, 0, 0, 1)">; });


</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">输出示例:</span>
pool-1-thread-1 出错了: java.lang.ArithmeticException: / by zero</pre>
</div>
</div>
</div>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">&nbsp;</div>
<p data-start="1361" data-end="1370"><strong data-start="1361" data-end="1367">特点</strong>:</p>
<ul data-start="1371" data-end="1500">
<li data-start="1371" data-end="1391">
<p data-start="1373" data-end="1391">给线程绑定一个全局的异常处理器。</p>
</li>
<li data-start="1392" data-end="1427">
<p data-start="1394" data-end="1427">任务里不用写 <code data-start="1401" data-end="1412">try/catch</code>,异常会被统一捕获和处理。</p>
</li>
<li data-start="1428" data-end="1454">
<p data-start="1430" data-end="1454">适用于 <code data-start="1434" data-end="1445">execute()</code> 提交的任务。</p>
</li>
<li data-start="1455" data-end="1500">
<p data-start="1457" data-end="1500">但对 <code data-start="1460" data-end="1470">submit()</code> 提交的异常无效(因为被封装到 <code data-start="1486" data-end="1494">Future</code> 里)。</p>
</li>
</ul>
<h2 data-start="1507" data-end="1533">(4) 重写 <code data-start="1517" data-end="1533">afterExecute()</code></h2>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="sticky top-9">
<div class="cnblogs_code">
<pre>ExecutorService pool = <span style="color: rgba(0, 0, 255, 1)">new</span> ThreadPoolExecutor(2, 4, 60<span style="color: rgba(0, 0, 0, 1)">,
      TimeUnit.SECONDS, </span><span style="color: rgba(0, 0, 255, 1)">new</span> LinkedBlockingQueue&lt;&gt;<span style="color: rgba(0, 0, 0, 1)">()) {
    @Override
    </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> afterExecute(Runnable r, Throwable t) {
      </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.afterExecute(r, t);
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (t != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
            System.err.println(</span>"execute 出错: " +<span style="color: rgba(0, 0, 0, 1)"> t);
      }
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (r <span style="color: rgba(0, 0, 255, 1)">instanceof</span> Future&lt;?&gt;<span style="color: rgba(0, 0, 0, 1)">) {
            </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
                ((Future</span>&lt;?&gt;<span style="color: rgba(0, 0, 0, 1)">) r).get();
            } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) {
                System.err.println(</span>"submit 出错: " +<span style="color: rgba(0, 0, 0, 1)"> e.getCause());
            }
      }
    }
};

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> execute 提交的异常</span>
pool.execute(() -&gt; { <span style="color: rgba(0, 0, 255, 1)">int</span> a = 1 / 0<span style="color: rgba(0, 0, 0, 1)">; });

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> submit 提交的异常</span>
pool.submit(() -&gt; { <span style="color: rgba(0, 0, 255, 1)">return</span> 1 / 0<span style="color: rgba(0, 0, 0, 1)">; });

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">输出</span>
execute 出错: java.lang.ArithmeticException: /<span style="color: rgba(0, 0, 0, 1)"> by zero
submit 出错: java.lang.ArithmeticException: </span>/ by zero</pre>
</div>
</div>
</div>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">&nbsp;</div>
<p data-start="2316" data-end="2325"><strong data-start="2316" data-end="2322">特点</strong>:</p>
<ul>
<li data-start="2316" data-end="2325"><code data-start="2328" data-end="2331">t</code> 参数能捕获 <code data-start="2338" data-end="2349">execute()</code> 的异常;</li>
<li data-start="2316" data-end="2325"><code data-start="2359" data-end="2373">Future.get()</code> 能捕获 <code data-start="2378" data-end="2388">submit()</code> 的异常;</li>
<li data-start="2316" data-end="2325">这样就能 <strong data-start="2403" data-end="2436">统一捕获 <code data-start="2410" data-end="2419">execute</code> 和 <code data-start="2422" data-end="2430">submit</code> 的异常</strong>;</li>
<li data-start="2316" data-end="2325">比单纯的 <code data-start="2447" data-end="2473">UncaughtExceptionHandler</code> 更全面。</li>
</ul>
<h1>10、说一下线程池有几种状态</h1>
<p data-start="79" data-end="96">(1) <strong data-start="83" data-end="94">RUNNING</strong></p>
<ul data-start="97" data-end="202">
<li data-start="97" data-end="130">
<p data-start="99" data-end="130">默认初始状态,可以接受新任务,也可以处理阻塞队列中的任务。</p>
</li>
<li data-start="131" data-end="202">
<p data-start="133" data-end="202">调用 <code data-start="136" data-end="148">shutdown()</code> → 转为 <strong data-start="154" data-end="166">SHUTDOWN</strong>;调用 <code data-start="170" data-end="185">shutdownNow()</code> → 转为 <strong data-start="191" data-end="199">STOP</strong>。</p>
</li>
</ul>
<p data-start="204" data-end="222">(2) <strong data-start="208" data-end="220">SHUTDOWN</strong></p>
<ul data-start="223" data-end="295">
<li data-start="223" data-end="258">
<p data-start="225" data-end="258">不再接受新任务,但会继续处理阻塞队列中的任务和正在执行的任务。</p>
</li>
<li data-start="259" data-end="295">
<p data-start="261" data-end="295">当队列为空且活动线程数为 0 → 转为 <strong data-start="281" data-end="292">TIDYING</strong>。</p>
</li>
</ul>
<p data-start="297" data-end="311">(3) <strong data-start="301" data-end="309">STOP</strong></p>
<ul data-start="312" data-end="404">
<li data-start="312" data-end="337">
<p data-start="314" data-end="337">不再接受新任务,也不处理阻塞队列中的任务。</p>
</li>
<li data-start="338" data-end="376">
<p data-start="340" data-end="376">会中断正在运行的任务(通过调用线程的 <code data-start="359" data-end="372">interrupt()</code>)。</p>
</li>
<li data-start="377" data-end="404">
<p data-start="379" data-end="404">一般由 <code data-start="383" data-end="398">shutdownNow()</code> 触发。</p>
</li>
</ul>
<p data-start="406" data-end="423">(4) <strong data-start="410" data-end="421">TIDYING</strong></p>
<ul data-start="424" data-end="500">
<li data-start="424" data-end="445">
<p data-start="426" data-end="445">所有任务都结束,活动线程数为 0。</p>
</li>
<li data-start="446" data-end="483">
<p data-start="448" data-end="483">会执行 <code data-start="452" data-end="466">terminated()</code> 钩子方法,通常用于清理资源。</p>
</li>
<li data-start="484" data-end="500">
<p data-start="486" data-end="500">是进入终止前的过渡状态。</p>
</li>
</ul>
<p data-start="502" data-end="522">(5) <strong data-start="506" data-end="520">TERMINATED</strong></p>
<ul data-start="523" data-end="544">
<li data-start="523" data-end="544">
<p data-start="525" data-end="544">线程池彻底终止,生命周期完全结束。</p>
</li>
</ul>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">      ┌───────────┐   shutdown()    ┌───────────┐
      │RUNNING│ ──────────────▶ │ SHUTDOWN│
      └─────┬─────┘               └─────┬─────┘
shutdownNow()│                           │
               ▼                           ▼
            ┌───────┐      任务清空      ┌───────────┐
            │ STOP│ ─────────────────▶ │ TIDYING   │
            └───────┘                     └─────┬─────┘
                                                 │ terminated()
                                                 ▼
                                          ┌───────────┐
                                          │TERMINATED │
                                          └───────────┘</span></pre>
</div>
<h1>11、线程池如何实现参数的动态修改</h1>
<p data-start="65" data-end="107">(1) <strong data-start="69" data-end="105">ThreadPoolExecutor 提供的 setter 方法</strong></p>
<ul data-start="108" data-end="399">
<li data-start="108" data-end="147">
<p data-start="110" data-end="147"><code data-start="110" data-end="145">setCorePoolSize(int corePoolSize)</code></p>
</li>
<li data-start="148" data-end="193">
<p data-start="150" data-end="193"><code data-start="150" data-end="191">setMaximumPoolSize(int maximumPoolSize)</code></p>
</li>
<li data-start="194" data-end="242">
<p data-start="196" data-end="242"><code data-start="196" data-end="240">setKeepAliveTime(long time, TimeUnit unit)</code></p>
</li>
<li data-start="243" data-end="294">
<p data-start="245" data-end="294"><code data-start="245" data-end="292">setThreadFactory(ThreadFactory threadFactory)</code></p>
</li>
<li data-start="295" data-end="399">
<p data-start="297" data-end="399"><code data-start="297" data-end="360">setRejectedExecutionHandler(RejectedExecutionHandler handler)</code><br data-start="360" data-end="363">
👉 这些方法可在运行时动态修改参数,但线程池本身不会主动感知变化。</p>


</li>


</ul>
<p data-start="406" data-end="424">(2) <strong data-start="410" data-end="422">配置中心动态下发</strong></p>
<ul data-start="425" data-end="537">
<li data-start="425" data-end="475">
<p data-start="427" data-end="475">使用 Nacos / Apollo / Spring Cloud Config 等配置中心。</p>


</li>
<li data-start="476" data-end="516">
<p data-start="478" data-end="516">配置变更后应用自动收到通知,调用 <code data-start="495" data-end="505">setXxx()</code> 方法完成热更新。</p>


</li>
<li data-start="517" data-end="537">
<p data-start="519" data-end="537">适合分布式环境,可全局统一调整。</p>


</li>


</ul>
<p data-start="544" data-end="561">(3) <strong data-start="548" data-end="559">自定义监听方案</strong></p>
<ul data-start="562" data-end="646">
<li data-start="562" data-end="596">
<p data-start="564" data-end="596">没有配置中心时,可以定时读取数据库/文件/Redis 配置。</p>


</li>
<li data-start="597" data-end="628">
<p data-start="599" data-end="628">如果参数发生变化,就调用 <code data-start="612" data-end="622">setXxx()</code> 更新。</p>


</li>
<li data-start="629" data-end="646">
<p data-start="631" data-end="646">简单易实现,但有一定延迟。</p>


</li>


</ul>
<p data-start="653" data-end="669">(4) <strong data-start="657" data-end="667">结合监控系统</strong></p>
<ul data-start="670" data-end="737">
<ul data-start="670" data-end="737">
<li data-start="670" data-end="704">
<p data-start="672" data-end="704">可通过队列长度、任务堆积、CPU 使用率等指标触发动态调整。</p>


</li>
<li data-start="705" data-end="737">
<p data-start="707" data-end="737">常见实践:监控 + 自动调节,或人工结合监控后台改配置。</p>

</li>

</ul>
</ul>
<h1>12、单机线程池执行断电了怎么处理</h1>
<p data-start="169" data-end="186">(1) <strong data-start="173" data-end="184">阻塞队列持久化</strong></p>
<ul data-start="187" data-end="264">
<li data-start="187" data-end="238">
<p data-start="189" data-end="238">默认的内存队列断电即丢失,可替换为消息队列(Kafka、RocketMQ、RabbitMQ)。</p>
</li>
<li data-start="239" data-end="264">
<p data-start="241" data-end="264">确保任务在宕机后依然存在,重启可继续消费。</p>
</li>
</ul>
<p data-start="266" data-end="287">(2) <strong data-start="270" data-end="285">正在处理任务的事务控制</strong></p>
<ul data-start="288" data-end="334">
<li data-start="288" data-end="309">
<p data-start="290" data-end="309">对数据库操作使用事务,保证原子性。</p>
</li>
<li data-start="310" data-end="334">
<p data-start="312" data-end="334">避免“库存减了但订单没落库”这类不一致。</p>
</li>
</ul>
<p data-start="336" data-end="358">(3) <strong data-start="340" data-end="356">断电任务的回滚与日志恢复</strong></p>
<ul data-start="359" data-end="410">
<li data-start="359" data-end="389">
<p data-start="361" data-end="389">在任务执行前记录操作日志,异常中断后可根据日志恢复。</p>
</li>
<li data-start="390" data-end="410">
<p data-start="392" data-end="410">常见于支付、退款等高一致性业务。</p>
</li>
</ul>
<p data-start="412" data-end="433">(4) <strong data-start="416" data-end="431">服务器重启后的任务恢复</strong></p>
<ul data-start="434" data-end="488">
<li data-start="434" data-end="470">
<p data-start="436" data-end="470">将待执行任务元信息存入数据库/文件,重启时加载并重新提交线程池。</p>
</li>
<li data-start="471" data-end="488">
<p data-start="473" data-end="488">常用于定时清算、对账任务。</p>
</li>
</ul>
<h1>13、Fork/Join框架了解吗</h1>
<h2 data-start="117" data-end="140">(1)什么是 Fork/Join 框架?</h2>
<ul data-start="141" data-end="389">
<li data-start="141" data-end="189">
<p data-start="143" data-end="189">Java7 引入的并行计算框架,位于 <code data-start="162" data-end="184">java.util.concurrent</code> 包。</p>
</li>
<li data-start="190" data-end="245">
<p data-start="192" data-end="245"><strong data-start="192" data-end="200">设计目标</strong>:充分利用多核 CPU,支持<strong data-start="214" data-end="242">大任务拆分(Fork)+ 小任务合并(Join)</strong>。</p>
</li>
<li data-start="246" data-end="389">
<p data-start="248" data-end="254">基础类:</p>
<ul data-start="257" data-end="389">
<li data-start="257" data-end="289">
<p data-start="259" data-end="289"><code data-start="259" data-end="273">ForkJoinPool</code> → 线程池,负责任务调度;</p>
</li>
<li data-start="292" data-end="389">
<p data-start="294" data-end="324"><code data-start="294" data-end="308">ForkJoinTask</code> → 抽象任务类,常用子类:</p>
<ul data-start="329" data-end="389">
<li data-start="329" data-end="357">
<p data-start="331" data-end="357"><code data-start="331" data-end="349">RecursiveTask&lt;V&gt;</code>:有返回值;</p>
</li>
<li data-start="362" data-end="389">
<p data-start="364" data-end="389"><code data-start="364" data-end="381">RecursiveAction</code>:无返回值。</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 data-start="396" data-end="406">(2)工作原理</h2>
<ol data-start="407" data-end="612">
<li data-start="407" data-end="432">
<p data-start="410" data-end="432"><strong data-start="410" data-end="418">分而治之</strong>:将大任务拆分成小任务。</p>
</li>
<li data-start="433" data-end="476">
<p data-start="436" data-end="476"><strong data-start="436" data-end="444">并行执行</strong>:小任务由 <code data-start="450" data-end="464">ForkJoinPool</code> 中的工作线程执行。</p>
</li>
<li data-start="477" data-end="519">
<p data-start="480" data-end="519"><strong data-start="480" data-end="488">结果合并</strong>:子任务完成后通过 <code data-start="498" data-end="506">join()</code> 返回结果,逐级汇总。</p>
</li>
<li data-start="520" data-end="612">
<p data-start="523" data-end="550"><strong data-start="523" data-end="547">工作窃取 (Work Stealing)</strong>:</p>
<ul data-start="554" data-end="612">
<li data-start="554" data-end="573">
<p data-start="556" data-end="573">每个工作线程维护一个双端队列;</p>
</li>
<li data-start="577" data-end="612">
<p data-start="579" data-end="612">当某个线程空闲,会从别的线程队列“偷”任务执行,避免线程闲置。</p>
</li>
</ul>
</li>
</ol>
<h2 data-start="619" data-end="629">(3)示例代码</h2>
<p data-start="630" data-end="667">求和 <code data-start="633" data-end="646">1+2+...+100</code>,设置阈值 <code data-start="652" data-end="666">THRESHOLD=16</code>:</p>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="sticky top-9">&nbsp;
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span> CountTask <span style="color: rgba(0, 0, 255, 1)">extends</span> RecursiveTask&lt;Integer&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> THRESHOLD = 16<span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> start, end;

    </span><span style="color: rgba(0, 0, 255, 1)">public</span> CountTask(<span style="color: rgba(0, 0, 255, 1)">int</span> start, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> end) {
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.start =<span style="color: rgba(0, 0, 0, 1)"> start;
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.end =<span style="color: rgba(0, 0, 0, 1)"> end;
    }

    @Override
    </span><span style="color: rgba(0, 0, 255, 1)">protected</span><span style="color: rgba(0, 0, 0, 1)"> Integer compute() {
      </span><span style="color: rgba(0, 0, 255, 1)">int</span> sum = 0<span style="color: rgba(0, 0, 0, 1)">;
      </span><span style="color: rgba(0, 0, 255, 1)">boolean</span> canCompute = (end - start) &lt;=<span style="color: rgba(0, 0, 0, 1)"> THRESHOLD;

      </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (canCompute) {
            </span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = start; i &lt;= end; i++<span style="color: rgba(0, 0, 0, 1)">) {
                sum </span>+=<span style="color: rgba(0, 0, 0, 1)"> i;
            }
      } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
            </span><span style="color: rgba(0, 0, 255, 1)">int</span> middle = (start + end) / 2<span style="color: rgba(0, 0, 0, 1)">;
            CountTask leftTask </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> CountTask(start, middle);
            CountTask rightTask </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> CountTask(middle + 1<span style="color: rgba(0, 0, 0, 1)">, end);

            leftTask.fork(); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 提交左任务</span>
            <span style="color: rgba(0, 0, 255, 1)">int</span> rightResult = rightTask.compute(); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 当前线程直接算右任务</span>
            <span style="color: rgba(0, 0, 255, 1)">int</span> leftResult = leftTask.join();      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 等待左任务结果</span>
<span style="color: rgba(0, 0, 0, 1)">
            sum </span>= leftResult +<span style="color: rgba(0, 0, 0, 1)"> rightResult;
      }
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> sum;
    }

    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> main(String[] args) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> Exception {
      ForkJoinPool pool </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ForkJoinPool();
      CountTask task </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> CountTask(1, 100<span style="color: rgba(0, 0, 0, 1)">);
      Future</span>&lt;Integer&gt; result =<span style="color: rgba(0, 0, 0, 1)"> pool.submit(task);
      System.out.println(</span>"结果: " +<span style="color: rgba(0, 0, 0, 1)"> result.get());
    }
}</span></pre>
</div>
</div>
</div>
<p data-start="1863" data-end="1866">输出:<code class="whitespace-pre!"><span class="hljs-section">结果: 5050</span></code></p>
<h2 data-start="1890" data-end="1900">(4)注意事项</h2>
<ol data-start="1901" data-end="2290">
<li data-start="1901" data-end="1958">
<p data-start="1904" data-end="1919"><strong data-start="1904" data-end="1917">不要无限制拆分任务</strong></p>
<ul data-start="1923" data-end="1958">
<li data-start="1923" data-end="1958">
<p data-start="1925" data-end="1958">设置合理的 <code data-start="1931" data-end="1942">THRESHOLD</code>,否则递归太深,性能反而差。</p>
</li>
</ul>
</li>
<li data-start="1959" data-end="2082">
<p data-start="1962" data-end="1983"><strong data-start="1962" data-end="1981">fork/join 的调用顺序</strong></p>
<ul data-start="1987" data-end="2082">
<li data-start="1987" data-end="2050">
<p data-start="1989" data-end="2050">推荐:<code data-start="1992" data-end="2048">leftTask.fork(); rightTask.compute(); leftTask.join();</code></p>
</li>
<li data-start="2054" data-end="2082">
<p data-start="2056" data-end="2082">避免所有子任务都 fork,导致线程间竞争严重。</p>
</li>
</ul>
</li>
<li data-start="2083" data-end="2133">
<p data-start="2086" data-end="2096"><strong data-start="2086" data-end="2094">异常处理</strong></p>
<ul data-start="2100" data-end="2133">
<li data-start="2100" data-end="2133">
<p data-start="2102" data-end="2133">子任务异常会传播到 <code data-start="2112" data-end="2120">join()</code> 时抛出,要注意捕获。</p>
</li>
</ul>
</li>
<li data-start="2134" data-end="2199">
<p data-start="2137" data-end="2146"><strong data-start="2137" data-end="2144">线程数</strong></p>
<ul data-start="2150" data-end="2199">
<li data-start="2150" data-end="2199">
<p data-start="2152" data-end="2199">默认 = CPU 核心数,通常够用;也可指定 <code data-start="2175" data-end="2196">new ForkJoinPool(n)</code>。</p>
</li>
</ul>
</li>
<li data-start="2200" data-end="2290">
<p data-start="2203" data-end="2217"><strong data-start="2203" data-end="2215">和普通线程池区别</strong></p>
<ul data-start="2221" data-end="2290">
<li data-start="2221" data-end="2249">
<p data-start="2223" data-end="2249">普通线程池 → 任务彼此独立,适合 IO 密集;</p>
</li>
<li data-start="2253" data-end="2290">
<p data-start="2255" data-end="2290">Fork/Join → 大任务拆小任务,适合 CPU 密集型计算。</p>
</li>
</ul>
</li>
</ol>
<h2 data-start="2297" data-end="2305">(5)总结</h2>
<ul data-start="2306" data-end="2478">
<li data-start="2306" data-end="2366">
<p data-start="2308" data-end="2366">Fork/Join 是 <strong data-start="2320" data-end="2333">并行递归计算的利器</strong>,适合大规模、可拆分的任务(如矩阵计算、归并排序、递归求和)。</p>
</li>
<li data-start="2367" data-end="2411">
<p data-start="2369" data-end="2411"><strong data-start="2369" data-end="2377">核心思想</strong>:任务拆分 + 线程池执行 + 结果合并 + 工作窃取负载均衡。</p>
</li>
<li data-start="2412" data-end="2478">
<p data-start="2414" data-end="2427">编写任务时要重点关注:</p>
<ul data-start="2430" data-end="2478">
<li data-start="2430" data-end="2441">
<p data-start="2432" data-end="2441">合理拆分任务;</p>
</li>
<li data-start="2444" data-end="2457">
<p data-start="2446" data-end="2457">避免过多的小任务;</p>
</li>
<li data-start="2460" data-end="2478">
<p data-start="2462" data-end="2478">注意异常处理和结果合并逻辑。</p>
</li>
</ul>
</li>
</ul>
<p>&nbsp;</p>
<h2>参考</h2>
<p> 沉默王二公众号</p><br><br>
来源:https://www.cnblogs.com/xiaoqian01/p/19091610
頁: [1]
查看完整版本: Java并发编程(5)