項(xiàng)目中經(jīng)常用到線程池,1000個人有1000鐘創(chuàng)建線程池的方式,先背書一段阿里ajva開發(fā)規(guī)范上的話:
【強(qiáng)制】線程池不允許使用 Executors 去創(chuàng)建,而是通過 ThreadPoolExecutor 的方式,這樣
的處理方式讓寫的同學(xué)更加明確線程池的運(yùn)行規(guī)則,規(guī)避資源耗盡的風(fēng)險(xiǎn)。
說明:Executors 返回的線程池對象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool:
允許的請求隊(duì)列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,從而導(dǎo)致 OOM。
2)CachedThreadPool 和 ScheduledThreadPool:
允許的創(chuàng)建線程數(shù)量為 Integer.MAX_VALUE,可能會創(chuàng)建大量的線程,從而導(dǎo)致 OOM。
今天來說說springboot中通過線程池的使用
springboot中通過注解線程池的使用
先創(chuàng)建一個自定義線程池
先創(chuàng)建一個自定義線程池, 繼承spring并發(fā)包里面提供ThreadPoolTaskExecutor,演示用,子類就不加別的邏輯了
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.concurrent.ListenableFuture;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
public class TestThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
@Override
public void execute(Runnable task) {
super.execute(task);
}
@Override
public void execute(Runnable task, long startTimeout) {
super.execute(task, startTimeout);
}
@Override
public Future<?> submit(Runnable task) {
return super.submit(task);
}
@Override
public <T> Future<T> submit(Callable<T> task) {
return super.submit(task);
}
@Override
public ListenableFuture<?> submitListenable(Runnable task) {
return super.submitListenable(task);
}
@Override
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
return super.submitListenable(task);
}
}
在創(chuàng)建一個線程config類,注入到spring容器里面
創(chuàng)建一個config類,在里面創(chuàng)建我們自定義的線程池,在里面設(shè)置線程池各種參數(shù),具體參數(shù)就說了,原來的文章里面
@Configuration
@EnableAsync
@Slf4j
public class ExecutorConfig {
@Bean
public Executor asyncServiceExecutor() {
log.info("start asyncServiceExecutor");
ThreadPoolTaskExecutor executor = new TestThreadPoolTaskExecutor();
//配置核心線程數(shù)
executor.setCorePoolSize(5);
//配置最大線程數(shù)
executor.setMaxPoolSize(200);
//配置隊(duì)列大小
executor.setQueueCapacity(10000);
//配置線程池中的線程的名稱前綴
executor.setThreadNamePrefix("test-thread-");
// rejection-policy:當(dāng)pool已經(jīng)達(dá)到max size的時候,如何處理新任務(wù)
// CALLER_RUNS:不在新線程中執(zhí)行任務(wù),而是有調(diào)用者所在的線程來執(zhí)行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//執(zhí)行初始化
executor.initialize();
return executor;
}
}
最后就是來通過注解使用線程池了
通過@Async注解,放到方法上,就可以把方法加入到線程池中去執(zhí)行,就和線程類的run方法一樣了。
@Async("asyncServiceExecutor")
@Async("asyncServiceExecutor"),這個里面的值,就是我們前面注冊到容器里面的線程池,來看一眼
看一眼spring容器視圖,這個線程池對象時我們注冊線程config類時候注入到容器里面的,讓我們來測試兩個方法吧,一個沒有返回的,一個有返回值,直接上代碼
@Component
public class TestTask {
@Async("asyncServiceExecutor")
public void task1(){
System.out.println("測試一個沒有返回值的任務(wù)1");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("測試一個沒有返回值的任務(wù)2");
}
@Async("asyncServiceExecutor")
public Future<String> task2(){
System.out.println("測試一個有返回值的任務(wù)1");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("測試一個有返回值的任務(wù)2");
//返回當(dāng)前線程的線程名稱
return new AsyncResult<>(Thread.currentThread().getName());
}
}
這個任務(wù)類中兩個任務(wù),一個沒有返回值的任務(wù)task1,就相當(dāng)于Runable線程,一個有返回值的任務(wù)task2,相當(dāng)于callable線程,我這邊用了AsyncResult做了返回值,看一眼AsyncResult,也是spring的并發(fā)包里面的AsyncResult
他實(shí)現(xiàn)了ListenableFuture接口,類關(guān)系如下:
來看一下task1沒有返回值任務(wù)的測試結(jié)果
測試類如下:
@RunWith( SpringRunner.class)
@SpringBootTest(classes = BootStartApplication.class)
public class TestTaskCase {
@Autowired
private TestTask testTask;
@Test
public void testVoid(){
//測試沒有返回值的任務(wù)
testTask.task1();
try {
System.out.println("正在測試沒有返回值的任務(wù)task1");
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
運(yùn)行結(jié)果如下:
看到結(jié)果了,就是把一個runable線程加入到了線程中執(zhí)行了
在來看一下task2有返回值任務(wù)的測試結(jié)果
測試類如下:
@RunWith( SpringRunner.class)
@SpringBootTest(classes = BootStartApplication.class)
public class TestTaskCase {
@Autowired
private TestTask testTask;
@Test
public void testVoid(){
Future<String> future = testTask.task2();
System.out.println("正在測試有返回值的任務(wù)task2");
while (true) {
//while true中判斷任務(wù)是否完成
if (future.isDone() ) {
System.out.println("task2任務(wù)已經(jīng)完成");
try {
System.out.println("task2任務(wù)已經(jīng)完成,返回值為:" + future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
break;
}
System.out.println("task2任務(wù)沒有完成正在等待");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
這里使用了Future的isDone()方法來判斷任務(wù)是否已經(jīng)完成,看下運(yùn)行結(jié)果:
就是吧一個callable線程加入到了線程中執(zhí)行
使用說完,再來說說給線程池添加監(jiān)控吧
線程池在用的時候,有時候會因?yàn)榫€程數(shù)量的一些問題,引發(fā)一些線上問題,所以對線程池添加一些監(jiān)控是很有必要的,記得我們自定義的那個線程吧,我那會說演示就不添加邏輯,我們需要的監(jiān)控就要加到那里面去了,直接上代碼吧
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.concurrent.ListenableFuture;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
public class TestThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
private void showThreadPoolInfo(String method) {
ThreadPoolExecutor threadPoolExecutor = getThreadPoolExecutor();
if (null == threadPoolExecutor) {
return;
}
//這里可以用使用存儲到在線庫、打日志、存緩存等方式把監(jiān)控?cái)?shù)據(jù)記錄下來
//用到線程池里面具體哪個方法執(zhí)行的線程任務(wù)
System.out.println("使用到的方法:" + method);
//是我們配置在config里面線程前綴的名字(用于多個線程池中區(qū)分線程池)
System.out.println("線程的前綴名字:" +
this.getThreadNamePrefix());
System.out.println("線程池中的任務(wù)數(shù)量:" +
threadPoolExecutor.getTaskCount());
System.out.println("線程池中已經(jīng)完成的任務(wù)數(shù):" +
threadPoolExecutor.getCompletedTaskCount());
System.out.println("線程池中的線程數(shù):" +
threadPoolExecutor.getActiveCount());
System.out.println("線程池中隊(duì)列的長度:" +
threadPoolExecutor.getQueue().size());
}
@Override
public void execute(Runnable task) {
showThreadPoolInfo("executeRunnable");
super.execute(task);
}
@Override
public void execute(Runnable task, long startTimeout) {
showThreadPoolInfo("executeRunnableStartTimeout");
super.execute(task, startTimeout);
}
@Override
public Future<?> submit(Runnable task) {
showThreadPoolInfo("submitRunnable");
return super.submit(task);
}
@Override
public <T> Future<T> submit(Callable<T> task) {
showThreadPoolInfo("submitCallable");
return super.submit(task);
}
@Override
public ListenableFuture<?> submitListenable(Runnable task) {
showThreadPoolInfo("submitListenableRunnable");
return super.submitListenable(task);
}
@Override
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
showThreadPoolInfo("submitListenableCallable");
return super.submitListenable(task);
}
}
這里主要用到了線程中的任務(wù)數(shù)、線程數(shù)等屬性,這個屬性可以自己按需要來添加,添加監(jiān)控的方式可以使用存儲到數(shù)據(jù)庫、緩存、打日志接flume、kafka等方式,加上了監(jiān)控日志,我們再來看下測試類運(yùn)行的結(jié)果:
這樣每次把任務(wù)添加到線程池中都會把這些線程池屬性進(jìn)行實(shí)時監(jiān)控,可以實(shí)時關(guān)注應(yīng)用的運(yùn)行情況,也可以出現(xiàn)線上問題時快速定位查詢
springboot通過注解使用線程池就為大家說到這里,歡迎大家來交流,指出文中一些說錯的地方,讓我加深認(rèn)識。
謝謝大家!