java線程池ExecutorService超時(shí)處理小結(jié)
場景問題:使用線程池ExecutorService,想設(shè)置每個(gè)子線程的執(zhí)行超時(shí)時(shí)間,使用future.get()來監(jiān)聽超時(shí),當(dāng)有子線程阻塞時(shí),導(dǎo)致有的隊(duì)列任務(wù)還未執(zhí)行就被取消了。
方式一、使用 future.get() 來監(jiān)聽超時(shí)取消
這種辦法看似能解決問題,但是當(dāng)任務(wù)累積處理不過來時(shí),會(huì)漏執(zhí)行。
比如下面的例子,就實(shí)際只會(huì)執(zhí)行一個(gè)子線程。
package com.study; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.concurrent.*; public class Test { private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private static final ExecutorService threadPool = Executors.newFixedThreadPool(1); public static void main(String[] args) throws Exception { for (int i = 0; i < 10; i++) { Future<?> future = threadPool.submit(new Runnable() { @Override public void run() { try { System.out.println(LocalDateTime.now().format(formatter)); Thread.sleep(5000); } catch (InterruptedException e) { // e.printStackTrace(); } } }); new Thread(new Runnable() { @Override public void run() { try { future.get(3, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } catch (TimeoutException e) {//超時(shí)異常 future.cancel(true); // 超時(shí)后取消任務(wù) } } }).start(); } } }
方式二、在子線程內(nèi)部,超時(shí)后去發(fā)送中斷信號(hào)
package com.study; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.concurrent.*; public class Test { private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private static final ExecutorService threadPool = Executors.newFixedThreadPool(1); private static final ScheduledExecutorService timeoutExecutor = new ScheduledThreadPoolExecutor(1);//監(jiān)聽超時(shí),這個(gè)數(shù)量要和線程池?cái)?shù)量相同 public static void main(String[] args) throws Exception { for (int i = 0; i < 10; i++) { // int delay = 3; int delay = i + 1; threadPool.submit(new Runnable() { @Override public void run() { ScheduledFuture<?> schedule = null; try { Thread thread = Thread.currentThread(); // 啟動(dòng)一個(gè)定時(shí)器,如果任務(wù)執(zhí)行超過3秒則中斷當(dāng)前線程 schedule = timeoutExecutor.schedule(() -> { thread.interrupt(); // 中斷當(dāng)前正在執(zhí)行的任務(wù) }, delay, TimeUnit.SECONDS); System.out.println(LocalDateTime.now().format(formatter)); Thread.sleep(5000); // FileOutputStream fos = new FileOutputStream("d:/test.txt" + k); // for (int j = 0; j < 1000000; j++) { // fos.write("123".getBytes()); // } // fos.close(); } catch (InterruptedException e) { // e.printStackTrace(); } finally { if (schedule != null) { //取消任務(wù) schedule.cancel(true); } } } }); } } }
這里其實(shí)還是有問題 ,把 Thread.sleep(5000);改成注釋的io阻塞,還是要等線程執(zhí)行結(jié)束后才會(huì)取消線程執(zhí)行。
所以單純使用 future 是實(shí)現(xiàn)不了這個(gè)場景的邏輯的。
timeoutExecutor 數(shù)量和 線程池?cái)?shù)量要一致的原因如下示例。
package com.study; import java.time.LocalDateTime; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class ScheduledExecutorServiceExample { private static final ScheduledExecutorService timeoutExecutor = new ScheduledThreadPoolExecutor(2); public static void main(String[] args) throws InterruptedException { // 調(diào)用schedule方法兩次 scheduleTask("Task 1"); scheduleTask("Task 2"); scheduleTask("Task 3"); } private static void scheduleTask(String taskName) { timeoutExecutor.schedule(() -> { System.out.println(taskName + " started at: " + LocalDateTime.now()); try { // 模擬任務(wù)執(zhí)行 Thread.sleep(2000); // 假設(shè)每個(gè)任務(wù)執(zhí)行2秒 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }, 3, TimeUnit.SECONDS); } }
方式三、自己定義鎖來實(shí)現(xiàn)
package com.study; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.concurrent.*; public class Test { private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private static final ExecutorService threadPool = Executors.newFixedThreadPool(16);//這里不能設(shè)置為1了,這里已經(jīng)不是用來控制并發(fā)數(shù)量了,只是為了重復(fù)利用線程 private static final ScheduledExecutorService timeoutExecutor = new ScheduledThreadPoolExecutor(1);//監(jiān)聽超時(shí),這個(gè)數(shù)量要和線程池?cái)?shù)量相同 public static void main(String[] args) throws Exception { for (int i = 0; i < 10; i++) { Thread.sleep(50); // int delay = 3; int delay = i + 1; int k = i; threadPool.submit(new Runnable() { @Override public void run() { ScheduledFuture<?> schedule = null; try { ThreadPool.awaitThread(); // 啟動(dòng)一個(gè)定時(shí)器,如果任務(wù)執(zhí)行超過3秒則中斷當(dāng)前線程 // timeoutExecutor如果只有一個(gè)線程池,這里面的代碼片段會(huì)阻塞,上一個(gè)線程在這里的代碼片段執(zhí)行完后,當(dāng)前線程才會(huì)執(zhí)行這里的代碼片段, // 但是影響不大,因?yàn)檫@里的代碼片段只是釋放動(dòng)作,一瞬間就會(huì)執(zhí)行完,所以影響不大, // 如果其他場景這里阻塞時(shí)間比較久,那么timeoutExecutor線程大小要和threadPool線程大小一致。 schedule = timeoutExecutor.schedule(() -> { System.out.println("釋放1"); ThreadPool.releaseThread(); }, delay, TimeUnit.SECONDS); System.out.println("【" + Thread.currentThread().getName() + "】" + LocalDateTime.now().format(formatter)); Thread.sleep(5000); // FileOutputStream fos = new FileOutputStream("d:/test.txt" + k); // for (int j = 0; j < 1000000; j++) { // fos.write("123".getBytes()); // } // fos.close(); } catch (Exception e) { System.out.println("異常"); } finally { if (schedule != null) { //cancel返回true任務(wù)還未執(zhí)行,需要取消任務(wù) if (schedule.cancel(true)) { System.out.println("釋放2"); ThreadPool.releaseThread(); } } } } }); } } }
package com.study; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 線程池 */ public class ThreadPool { private static final int MAX_POOL_SIZE = 1; // 最大線程數(shù),控制并發(fā)數(shù)量 private static int totalThread = 0; // 總線程數(shù) private static final Lock lock = new ReentrantLock(true); private static final Condition notice = lock.newCondition(); /** * 從線程池獲取線程 */ public static boolean awaitThread() { lock.lock(); try { // 嘗試從線程池中獲取線程 if (totalThread < MAX_POOL_SIZE) { totalThread++; return true; } // 線程已到達(dá)最大線程數(shù),等待歸還線程,最長等待1小時(shí),await()會(huì)釋放當(dāng)前線程的鎖 if (notice.await(1, TimeUnit.HOURS)) { totalThread++; return true; } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } return false; } /** * 釋放線程到線程池 */ public static void releaseThread() { lock.lock(); try { totalThread--; // 通知有空閑,signal()會(huì)喚醒其中一個(gè)await()線程 notice.signal(); } finally { lock.unlock(); } } }
到此這篇關(guān)于java線程池ExecutorService超時(shí)處理小結(jié)的文章就介紹到這了,更多相關(guān)java線程池ExecutorService超時(shí)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java開啟遠(yuǎn)程debug竟有兩種參數(shù)(最新推薦)
這篇文章主要介紹了java開啟遠(yuǎn)程debug竟有兩種參數(shù),本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07SpringBoot3通過GraalVM生成exe執(zhí)行文件問題
文章介紹了如何安裝GraalVM和Visual Studio,并通過Spring Boot項(xiàng)目將Java應(yīng)用程序封裝成可執(zhí)行文件(.exe)2024-12-12java實(shí)現(xiàn)將漢語轉(zhuǎn)換為拼音功能
這篇文章主要介紹了java實(shí)現(xiàn)將漢語轉(zhuǎn)換為拼音功能,非常不錯(cuò),具有參考借鑒價(jià)值 ,需要的朋友可以參考下2017-05-05SpringBoot使用Jasypt對(duì)YML文件配置內(nèi)容加密的方法(數(shù)據(jù)庫密碼加密)
本文介紹了如何在SpringBoot項(xiàng)目中使用Jasypt對(duì)application.yml文件中的敏感信息(如數(shù)據(jù)庫密碼)進(jìn)行加密,通過引入Jasypt依賴、配置加密密鑰、加密敏感信息并測(cè)試解密功能,可以提高配置文件的安全性,減少因配置文件泄露導(dǎo)致的安全風(fēng)險(xiǎn),感興趣的朋友一起看看吧2025-03-03Java創(chuàng)建多線程的幾種方式實(shí)現(xiàn)
這篇文章主要介紹了Java創(chuàng)建多線程的幾種方式實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-1010k+點(diǎn)贊的 SpringBoot 后臺(tái)管理系統(tǒng)教程詳解
這篇文章主要介紹了10k+點(diǎn)贊的 SpringBoot 后臺(tái)管理系統(tǒng)教程詳解,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01Java HttpClient實(shí)現(xiàn)socks代理的示例代碼
這篇文章主要介紹了Java HttpClient 實(shí)現(xiàn) socks 代理的示例代碼,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下2020-11-11