Java多線程并發(fā)之線程池任務(wù)請(qǐng)求攔截測(cè)試實(shí)例
一、需求
前端會(huì)傳入一個(gè)存儲(chǔ)編碼的list,后臺(tái)接收到編碼通過(guò)計(jì)算返回每個(gè)編碼對(duì)應(yīng)的值,每個(gè)編碼計(jì)算出來(lái)的值是固定不變的。
二、設(shè)計(jì)方案
因?yàn)榍岸苏?qǐng)求響應(yīng)有一個(gè)時(shí)常要求,比如100ms。
而這個(gè)計(jì)算比較耗時(shí),因此為了請(qǐng)求能夠快速響應(yīng),在第一個(gè)請(qǐng)求過(guò)來(lái)時(shí)判斷redis緩存是否存儲(chǔ)編碼對(duì)應(yīng)的計(jì)算值,如果沒(méi)有就直接返回空,前端根據(jù)這個(gè)空值使用補(bǔ)償方案的默認(rèn)值。
后臺(tái)通過(guò)線程池執(zhí)行計(jì)算方法,然后存入redis,這樣下次用戶帶著相同的編碼請(qǐng)求就可以直接從緩存獲取,不用重復(fù)計(jì)算。
這里的問(wèn)題在于,當(dāng)并發(fā)量高的情況下,比如50個(gè)用戶帶著相同的編碼調(diào)用計(jì)算方法,而實(shí)際上計(jì)算方法只需要調(diào)用一次就可以了。
因此我們需要在將任務(wù)提交到線程池之前判斷線程池中執(zhí)行線程的數(shù)量來(lái)決定是否要將任務(wù)提交到線程池。
另外這里千萬(wàn)不能使用直接創(chuàng)建線程的方式,這會(huì)導(dǎo)致并發(fā)情況下突然創(chuàng)建大量線程,導(dǎo)致系統(tǒng)cpu飆升卡死。
三、測(cè)試
1、線程池實(shí)現(xiàn)類,提供全局唯一的線程池實(shí)例
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 固定大小的線程池
*/
public class DisCardThreadPool
{
private static DisCardThreadPool disCardThreadPool=new DisCardThreadPool();
/*
* 將構(gòu)造方法訪問(wèn)修飾符設(shè)為私有,禁止任意實(shí)例化。
*/
private DisCardThreadPool() {
}
/**
* 核心線程數(shù)
*/
int corePoolSize = 1;
/**
* 最大線程數(shù)
*/
int maximumPoolSize = 1;
/**
* 空閑線程存活時(shí)間
*/
long keepAliveTime = 10;
/*
* 線程池單例創(chuàng)建方法
*/
public static DisCardThreadPool newInstance() {
return disCardThreadPool;
}
private final ThreadPoolExecutor mThreadPool=new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<>(10), new ThreadPoolExecutor.DiscardPolicy());
public void execute(Runnable r){
mThreadPool.execute(r);
}
/**
* 隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目
* @return
*/
public synchronized int getQueue(){
return mThreadPool.getQueue().size();
}
/*
* 獲取線程池中剩余線程數(shù)目
* 獲取的結(jié)果不準(zhǔn)確
*/
public synchronized int getActiveCount(){
return mThreadPool.getActiveCount();
}2、測(cè)試類
這里通過(guò)CountDownLatch類同時(shí)啟動(dòng)多個(gè)線程來(lái)模擬并發(fā)請(qǐng)求
import com.teriste.service.threadpool.DisCardThreadPool;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
/**
* 模擬并發(fā)向線程池提交任務(wù)。
* 需求:使得線程池滿之后其他請(qǐng)求都不執(zhí)行
*/
public class MultiThreadConcurrencyTest {
//獲取線程池實(shí)例
private static DisCardThreadPool threadPool=DisCardThreadPool.newInstance();
@Test
public void test(){
//創(chuàng)建大小20的計(jì)數(shù)器,使得20個(gè)線程同時(shí)執(zhí)行,模擬并發(fā)
CountDownLatch countDownLatch=new CountDownLatch(20);
for (int i=0;i<20;i++){
InvokeThread thread=new InvokeThread(countDownLatch);
System.out.println("創(chuàng)建線程:"+thread.getName());
thread.start();
//啟動(dòng)一個(gè)線程,計(jì)數(shù)器就減一,同時(shí)在線程的run方法中阻塞線程,等待計(jì)數(shù)器喚醒
countDownLatch.countDown();
}
try {
//阻塞主線程,防止子線程還沒(méi)執(zhí)行主線程結(jié)束導(dǎo)致子線程無(wú)法執(zhí)行
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//線程池執(zhí)行線程
public static void invokeThread(){
//當(dāng)排隊(duì)隊(duì)列有線程等待時(shí)不繼續(xù)添加線程
synchronized (MultiThreadConcurrencyTest.class){
//System.out.println("等待隊(duì)列大小:"+threadPool.getQueue());
//官方api指出getActiveCount()無(wú)法獲取準(zhǔn)確的存獲線程數(shù)
//因?yàn)檫@里是根據(jù)隊(duì)列中待執(zhí)行任務(wù)數(shù)來(lái)判斷,因此如果線程池大小為1,實(shí)際上會(huì)有兩個(gè)線程被執(zhí)行,
//一個(gè)線程是進(jìn)入線程池,還有一個(gè)線程判斷此時(shí)隊(duì)列待執(zhí)行線程數(shù)是0會(huì)進(jìn)入待執(zhí)行隊(duì)列,因此最終執(zhí)行線程數(shù)是線程池大小+1
System.out.println("排隊(duì)隊(duì)列中的線程個(gè)數(shù):"+threadPool.getQueue());
if (threadPool.getQueue()<=0){
threadPool.execute(new WorkThread());
}
}
}
}
//調(diào)用線程池執(zhí)行任務(wù)的類,模擬外部請(qǐng)求實(shí)體發(fā)起請(qǐng)求
class InvokeThread extends Thread{
private CountDownLatch countDownLatch;
public InvokeThread(CountDownLatch countDownLatch){
this.countDownLatch=countDownLatch;
}
@Override
public void run(){
try {
//等待計(jì)數(shù)器喚醒
countDownLatch.await();
//向線程池提交線程
MultiThreadConcurrencyTest.invokeThread();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//任務(wù)類
class WorkThread implements Runnable{
@Override
public void run() {
String path="E:\\測(cè)試";
File file=new File(path);
if(!file.exists()){
file.mkdirs();//創(chuàng)建目錄
}
String fileName=Thread.currentThread().getName()+System.currentTimeMillis();
File newFile=new File(path,fileName);
try {
newFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
//在線程池中有可能是不同的線程使用相同的名稱
//因?yàn)榫€程池中上個(gè)結(jié)束的線程繼續(xù)使用來(lái)執(zhí)行下個(gè)線程
System.out.println("當(dāng)前執(zhí)行的線程的名稱:"+Thread.currentThread().getName()+fileName);
}
}

3、如果放開(kāi)對(duì)排隊(duì)隊(duì)列的判斷可以看到,當(dāng)線程池滿了之后執(zhí)行的是ArrayBlockingQueue.offer(E e);方法:

這說(shuō)明我們可以通過(guò)繼承ArrayBlockingQueue類實(shí)現(xiàn)自己的排隊(duì)隊(duì)列,當(dāng)線程池滿了之后調(diào)用offer方法時(shí),我們直接丟棄任務(wù)什么都不做,這樣就可以準(zhǔn)確實(shí)現(xiàn)上面的方案,并且可以去掉對(duì)隊(duì)列中待執(zhí)行線程的判斷,從而不需要加鎖,提高執(zhí)行效率。
下面是自定義隊(duì)列的實(shí)現(xiàn):
import java.util.Collection;
import java.util.concurrent.ArrayBlockingQueue;
/**
* 線程池使用該類時(shí)執(zhí)行插入方法時(shí)不會(huì)向隊(duì)列中插入數(shù)據(jù),會(huì)直接丟棄或記錄日志
*/
public class EmptyArrayBlockingQueue<E> extends ArrayBlockingQueue{
public EmptyArrayBlockingQueue(int capacity) {
super(capacity);
}
public EmptyArrayBlockingQueue(int capacity, boolean fair) {
super(capacity, fair);
}
public EmptyArrayBlockingQueue(int capacity, boolean fair, Collection c) {
super(capacity, fair, c);
}
/**
* 注意這里重寫的父類方法參數(shù)是泛型參數(shù)
* 由于Java的類型擦除,在編譯時(shí)會(huì)自動(dòng)變?yōu)镺bject類型
* 因此這里使用Object類型實(shí)際上就是重寫的父類方法
* @param e
* @return
*/
@Override
public boolean offer(Object e) {
/**不執(zhí)行將線程加入隊(duì)列的操作,這樣隊(duì)列永遠(yuǎn)為空
超過(guò)線程池核心線程數(shù)的線程實(shí)際上在這里都被丟棄了
可以增加記錄日志的操作
*/
return true;
}
}下面是修改后的線程池類:
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 固定大小的線程池
*/
public class DisCardThreadPool
{
private static DisCardThreadPool disCardThreadPool=new DisCardThreadPool();
/*
* 將構(gòu)造方法訪問(wèn)修飾符設(shè)為私有,禁止任意實(shí)例化。
*/
private DisCardThreadPool() {
}
/**
* 核心線程數(shù)
*/
int corePoolSize = 1;
/**
* 最大線程數(shù)
*/
int maximumPoolSize = 1;
/**
* 空閑線程存活時(shí)間
*/
long keepAliveTime = 10;
/*
* 線程池單例創(chuàng)建方法
*/
public static DisCardThreadPool newInstance() {
return disCardThreadPool;
}
private final ThreadPoolExecutor mThreadPool=new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime, TimeUnit.MILLISECONDS,new EmptyArrayBlockingQueue<>(10),
new ThreadPoolExecutor.DiscardPolicy());
public void execute(Runnable r){
mThreadPool.execute(r);
}
/**
* 隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目
* @return
*/
public synchronized int getQueue(){
return mThreadPool.getQueue().size();
}
/*
* 獲取線程池中剩余線程數(shù)目
* 獲取的結(jié)果不準(zhǔn)確
*/
public synchronized int getActiveCount(){
return mThreadPool.getActiveCount();
}
}下面是測(cè)試類:
import com.teriste.service.threadpool.DisCardThreadPool;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
/**
* 模擬并發(fā)向線程池提交任務(wù)。
* 需求:使得線程池滿之后其他請(qǐng)求都不執(zhí)行
*/
public class MultiThreadConcurrencyTest {
//獲取線程池實(shí)例
private static DisCardThreadPool threadPool=DisCardThreadPool.newInstance();
@Test
public void test(){
//創(chuàng)建大小20的計(jì)數(shù)器,使得20個(gè)線程同時(shí)執(zhí)行,模擬并發(fā)
CountDownLatch countDownLatch=new CountDownLatch(20);
for (int i=0;i<20;i++){
InvokeThread thread=new InvokeThread(countDownLatch);
System.out.println("創(chuàng)建線程:"+thread.getName());
thread.start();
//啟動(dòng)一個(gè)線程,計(jì)數(shù)器就減一,同時(shí)在線程的run方法中阻塞線程,等待計(jì)數(shù)器喚醒
countDownLatch.countDown();
}
try {
//阻塞主線程,防止子線程還沒(méi)執(zhí)行主線程結(jié)束導(dǎo)致子線程無(wú)法執(zhí)行
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//線程池執(zhí)行線程
public static void invokeThread(){
//當(dāng)排隊(duì)隊(duì)列有線程等待時(shí)不繼續(xù)添加線程
synchronized (MultiThreadConcurrencyTest.class){
//System.out.println("等待隊(duì)列大小:"+threadPool.getQueue());
//官方api指出getActiveCount()無(wú)法獲取準(zhǔn)確的存獲線程數(shù)
//因?yàn)檫@里是根據(jù)隊(duì)列中待執(zhí)行任務(wù)數(shù)來(lái)判斷,因此如果線程池大小為1,實(shí)際上會(huì)有兩個(gè)線程被執(zhí)行,
//一個(gè)線程是進(jìn)入線程池,還有一個(gè)線程判斷此時(shí)隊(duì)列待執(zhí)行線程數(shù)是0會(huì)進(jìn)入待執(zhí)行隊(duì)列,因此最終執(zhí)行線程數(shù)是線程池大小+1
System.out.println("排隊(duì)隊(duì)列中的線程個(gè)數(shù):"+threadPool.getQueue());
//if (threadPool.getQueue()<=0){
threadPool.execute(new WorkThread());
//}
}
}
}
//調(diào)用線程池執(zhí)行任務(wù)的類,模擬外部請(qǐng)求實(shí)體發(fā)起請(qǐng)求
class InvokeThread extends Thread{
private CountDownLatch countDownLatch;
public InvokeThread(CountDownLatch countDownLatch){
this.countDownLatch=countDownLatch;
}
@Override
public void run(){
try {
//等待計(jì)數(shù)器喚醒
countDownLatch.await();
//向線程池提交線程
MultiThreadConcurrencyTest.invokeThread();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//任務(wù)類
class WorkThread implements Runnable{
@Override
public void run() {
String path="E:\\測(cè)試";
File file=new File(path);
if(!file.exists()){
file.mkdirs();//創(chuàng)建目錄
}
String fileName=Thread.currentThread().getName()+System.currentTimeMillis();
File newFile=new File(path,fileName);
try {
newFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
//在線程池中有可能是不同的線程使用相同的名稱
//因?yàn)榫€程池中上個(gè)結(jié)束的線程繼續(xù)使用來(lái)執(zhí)行下個(gè)線程
System.out.println("當(dāng)前執(zhí)行的線程的名稱:"+Thread.currentThread().getName()+fileName);
}
}測(cè)試結(jié)果:

從測(cè)試結(jié)果可以看到,隊(duì)列中永遠(yuǎn)沒(méi)有線程被加入,即使線程池已滿,也不會(huì)導(dǎo)致被加入排隊(duì)隊(duì)列,實(shí)現(xiàn)了只有線程池存在空閑線程的時(shí)候才會(huì)接受新任務(wù)的需求。
到此這篇關(guān)于Java多線程并發(fā)之線程池任務(wù)請(qǐng)求攔截測(cè)試實(shí)例的文章就介紹到這了,更多相關(guān)Java線程池任務(wù)請(qǐng)求攔截測(cè)試內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- springboot項(xiàng)目攔截前端請(qǐng)求中的特殊字符串(解決方案)
- Springboot中攔截GET請(qǐng)求獲取請(qǐng)求參數(shù)驗(yàn)證合法性核心方法
- Spring MVC攔截器和跨域請(qǐng)求使用詳解
- Spring?RestTemplate如何利用攔截器打印請(qǐng)求參數(shù)和返回狀態(tài)
- Spring?Boot攔截器和監(jiān)聽(tīng)器實(shí)現(xiàn)對(duì)請(qǐng)求和響應(yīng)處理實(shí)戰(zhàn)
- SpringBoot基于過(guò)濾器和內(nèi)存實(shí)現(xiàn)重復(fù)請(qǐng)求攔截功能
相關(guān)文章
SpringSession會(huì)話管理之Redis與JDBC存儲(chǔ)實(shí)現(xiàn)方式
本文將詳細(xì)介紹Spring Session的核心概念、特性以及如何使用Redis和JDBC來(lái)實(shí)現(xiàn)會(huì)話存儲(chǔ),幫助開(kāi)發(fā)者構(gòu)建更加健壯和可擴(kuò)展的應(yīng)用系統(tǒng),希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-04-04
Java TimeoutException:服務(wù)調(diào)用超時(shí)異常的正確解決方案
在現(xiàn)代軟件開(kāi)發(fā)中,服務(wù)間通信是構(gòu)建分布式系統(tǒng)的基礎(chǔ),然而,網(wǎng)絡(luò)延遲、服務(wù)負(fù)載、資源競(jìng)爭(zhēng)等因素都可能導(dǎo)致服務(wù)調(diào)用超時(shí),TimeoutException是Java中表示服務(wù)調(diào)用超時(shí)的常見(jiàn)異常之一,本文將探討TimeoutException的成因及解決方案,需要的朋友可以參考下2024-12-12
SpringBoot整合POI導(dǎo)出通用Excel的方法示例
這篇文章主要介紹了SpringBoot整合POI導(dǎo)出通用Excel的方法示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
聊聊@Autowired注解注入,寫接口名字還是實(shí)現(xiàn)類的名字
這篇文章主要介紹了聊聊@Autowired注解注入,寫接口名字還是實(shí)現(xiàn)類的名字,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11

