序文
前回の記事 [スレッド プールの不適切な使用の危険性 (1): ローカル変数スレッド プールとクラス変数スレッド プールの使用方法] では、スレッド プールの使用方法の問題について議論し、新しいローカルはスレッド プールで使用できないという結論を得ました。可変スレッド プールの基本的な使用規則はすでにご存知だと思います。したがって、この記事では、Web プログラムがスレッド プールを再利用せず、相対的な同時実行性の下で制限なしでローカル スレッド プールを作成した場合に、Web プログラムが引き起こす問題を視覚化し、直感的に体験するためのツールを使用します。
さて、早速始めましょう。まず私のアイデアを紹介します。
1. jconsole ツールを使用して、プログラムのスレッド作成を視覚的に観察します
2. ローカル変数スレッド プールとクラス変数定義スレッド プールの 2 つのテスト メソッドをそれぞれ記述します。そして http リクエストをシミュレートするメソッドを記述し、記述したテストメソッドを継続的に呼び出します
3. jconsole のスレッド作成状況を観察します
以下がスレッド作成の最終イメージです (スレッドの再利用方法が一目でわかります)プールは大量のスレッドを作成しません。)
意味を理解した友人は、コードを直接スキップして最終的な結論を見ることができます。コードをテストすることは重要ではありません。
テストコード
クラス変数スレッドプール
/**
* @author: 代码丰
* @Date: 2023
*/
@Slf4j
@RestController
public class StaticThreadPoolController {
@Autowired
GenerateService circleGenerateService;
//计算最终的时间
private static long totalTime = 0L;
//计算最终调用次数
private static long totalLoopNumber = 0L;
//创建线程池 核心最大 都设置为了10
private static final ThreadPoolExecutor executorService = new ThreadPoolExecutor(10, 10, 10, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1),new ThreadPoolExecutor.AbortPolicy());
static {
executorService.setThreadFactory(new NameCustomizedThreadFactory("测试"));
}
@RequestMapping("/static")
public String test() throws Exception{
long begin = System.currentTimeMillis();
executorService.allowCoreThreadTimeOut(true);
ArrayList<Future<Boolean>> futures = Lists.newArrayList();
//每一个界面请求都会调用1000次的打印方法
for (int i = 1; i <= 1000; i++) {
Future<Boolean> submit = executorService.submit(() -> {
circleGenerateService.circleGenerateCurrentThread();
return true;
});
futures.add(submit);
}
//计算最后成功的笔数
long count = futures.stream().map(s -> {
try {
return s.get();
} catch (Exception e) {
return false;
}
}).filter(s -> s).count();
log.info("执行完成,共【{}】笔", count);
totalLoopNumber++;
//为了区分每一笔的过程,停顿下
Thread.sleep(100);
long end = System.currentTimeMillis();
totalTime += (end-begin);
return "总次数:"+count+"循环调用次数:"+totalLoopNumber+"用时:"+ totalTime +"毫秒";
}
}
ローカル変数スレッドプール
/**
* @author: 代码丰
* @Date: 2023
*/
@Slf4j
@RestController
public class LocalThreadPoolController {
@Autowired
GenerateService circleGenerateService;
//计算最终的时间
private static long totalTime = 0L;
//计算最终调用次数
private static long totalLoopNumber = 0L;
@RequestMapping ("/nonStatic")
public String test() throws Exception{
long begin = System.currentTimeMillis();
//创建线程池 核心最大 都设置为了10
ThreadPoolExecutor executorService = new ThreadPoolExecutor(10, 10, 5, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),new ThreadPoolExecutor.AbortPolicy());
//允许核心消亡
executorService.allowCoreThreadTimeOut(true);
ArrayList<Future<Boolean>> futures = Lists.newArrayList();
//每一个界面请求都会调用1000次的打印方法
for (int i = 1; i <= 1000; i++) {
Future<Boolean> submit = executorService.submit(() -> {
circleGenerateService.circleGenerateCurrentThread();
return true;
});
futures.add(submit);
}
//计算最后成功的笔数
long count = futures.stream().map(s -> {
try {
return s.get();
} catch (Exception e) {
return false;
}
}).filter(s -> s).count();
log.info("执行完成,共【{}】笔", count);
totalLoopNumber++;
executorService.shutdown();
//为了区分每一笔的过程,停顿下
Thread.sleep(100);
long end = System.currentTimeMillis();
totalTime += (end-begin);
return "总次数:"+count+"循环调用次数:"+totalLoopNumber+"用时:"+ totalTime +"毫秒";
}
}
httpリクエストをシミュレートする
/**
* @author: 代码丰
* @Date: 2023
*/
public class HttpUtils {
public static void testPost() throws IOException {
// URL url = new URL("http://localhost:8090/nonStatic");
URL url = new URL("http://localhost:8090/static");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setRequestMethod("POST");
OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream(), "8859_1");
out.write("username=kevin&password=*********");
out.flush();
out.close();
String sCurrentLine;
String sTotalString;
sCurrentLine = "";
sTotalString = "";
InputStream l_urlStream;
l_urlStream = connection.getInputStream();
BufferedReader l_reader = new BufferedReader(new InputStreamReader(
l_urlStream));
while ((sCurrentLine = l_reader.readLine()) != null) {
sTotalString += sCurrentLine + "/r/n";
}
System.out.println(sTotalString);
}
public static void main(String[] args) throws IOException {
for ( int a = 0; a<10; a++){
testPost();
}
}
}
印刷するために各スレッドが何をしなければならないか
/**
* @author: 代码丰
* @Date: 2023
*/
@Component
public class GenerateService {
private static int number = 1;
public String circleGenerateCurrentThread(){
System.out.println(Thread.currentThread().getName() + "打印 "+ number+++"次");
try{
Thread.sleep(100);
}catch (Exception e){
}
return "打印次数"+number;
}
}
結論は:
スレッドプールの変更:
静的に再利用されるスレッド プールを使用すると、大量のスレッドが作成されませんが、
ローカル変数 new を使用するスレッド プールを使用すると、大量のスレッドが作成されます。
記憶の変化
多様な探索:
他にも掘り下げてみたい質問があります。
1. jsonsoleの基本的な使い方
回答: JConsole は、組み込みの Java パフォーマンス アナライザーです。jdk インストール ディレクトリの下にある bin ディレクトリを見つけて、ダブルクリックして実行するだけです。
2. ブラウザから開始された各リクエストを処理する処理スレッドはどのように作成されますか?
回答: Web プログラムは通常 Tomcat コンテナーで実行されることを 忘れてはなりません。Tomcat 自体はマルチスレッドをサポートしています。つまり、Tomcat はスレッド プールを使用します。ユーザーが Web リソースへのアクセス要求を開始するとき、スレッド プールがある場合は、アイドル状態のスレッドの場合、リクエストを処理するためにスレッド プールからワーカー スレッドが取得されます。ワーカー スレッドがリクエストを処理中になると、リクエストが処理されるまで他のリクエストはワーカー スレッドに割り当てられません。
各リクエストが対応するスレッドによって処理されることがわかります。
3. スレッド プール内のコア スレッドの数は多いほど良いのですか? どのシナリオでコアスレッドを大きく設定できますか?
著者は実際にこれを行う方法についてはあまり知りません。具体的な作業では、パフォーマンスの最適化は行われていません。理論的な根拠しかありません。知っている人がいたら、コメント エリアでアドバイスをお願いします。(ここに理論上のリンクがあります。クリックしてジャンプします)
最終的な結論:
このことから、スレッド プールを使用する中心的な考え方は再利用であると結論付けることができます。スレッド プールは再利用されないので、このプールを作成することに何の意味があるのでしょうか? 各リクエストを処理するために新しいスレッドを開くことと違いはありません。