繰り返し発生する問題
典型的なオンライン春ブーツWeb項目を仮定し、サービスの処理ロジックは次のとおりです。
文字列パラメータ名を受け入れ、そして被験者に与えられた値は、Beanを注入し、Beanはプロパティを変更して、オブジェクト名を返すには、期間は、我々が使用Thread.sleep(300)
高い時間のかかるビジネスラインをシミュレートします
コードは以下の通りであります:
@RestController
@RequestMapping("name")
public class NameController {
@Autowired
private NameService nameService;
@RequestMapping("")
public String changeAndReadName (@RequestParam String name) throws InterruptedException {
System.out.println("get new request: " + name);
nameService.setName(name);
Thread.sleep(300);
return nameService.getName();
}
}
ネームサービスの上に、また、春・サービスの共通の目標は非常に簡単です
具体的なコードは次のよう:
@Service
public class NameService {
private String name;
public NameService() {
}
public NameService(String name) {
this.name = name;
}
public String getName() {
return name;
}
public NameService setName(String name) {
this.name = name;
return this;
}
}
私は実際には何の問題を実行していないではないだろう、テストが駆け抜けるもある使用済みの春ブーツパートナーがこのコードについての質問を持っていると信じていますが、実際にはライン上で、彼らはスレッド安全性の問題を生じさせる内
NameControllerをテストするためのオープン200件のスレッドがに再現することができ、我々はスレッドプールを渡され、それを信じてはいけません
次のようにテストコード
@Test
public void changeAndReadName() throws Exception {
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(200, 300 , 2000, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(200));
for (int i = 0; i < 200; i++) {
poolExecutor.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " begin");
Map<String, String> headers = new HashMap<String, String>();
Map<String, String> querys = new HashMap<String, String>();
querys.put("name", Thread.currentThread().getName());
headers.put("Content-Type", "text/plain;charset=UTF-8");
HttpResponse response = HttpTool.doGet("http://localhost:8080",
"/name",
"GET",
headers,
querys);
String res = EntityUtils.toString(response.getEntity());
if (!Thread.currentThread().getName().equals(res)) {
System.out.println("WE FIND BUG !!!");
Assert.assertEquals(true, false);
} else {
System.out.println(Thread.currentThread().getName() + " get received " + res);
}
}catch (Exception e) {
e.printStackTrace();
}
}
});
}
while(true) {
Thread.sleep(100);
}
}
戻り値の値が一致しない提出されている場合、このテストコードは、NameControllerテストのために、200個のスレッドを開始、提出するパラメータとして、自身の名をスレッドと主張し、結果を返します。各スレッドは、例外がAssertNotEqualsにスローされます
実際のテストの後、我々は、ほぼ200スレッドの半分以上は、例外がスローされますことを発見しました
問題が発生します
まず、我々は、分析する必要があるときに、スレッド、にhttp:// localhost:8080 /名前を要求する場合、春ブートサービスラインは、を通じて、この要求を受信するために8.5になります内蔵のTomcatの
Tomcatの8.5では、デフォルトは、NIOの実装が使用され、サーバ及び要求スレッドに対応するそれぞれに対応する再分配サーブレット処理要求に、サーバ・スレッド
だから我々は、その200個の同時クライアント要求を想定し、要求のNameController実行を入力し、それが処理する200件の別のサーバスレッドに分かれていることができます
しかし、それは、デフォルトでは、ネームサービスとのNameControllerがオブジェクトをシングルトンに属している、ビーンは、その春のオファーをオブジェクトと、そのスレッドの安全性のデフォルトの実装ではありません
この時間は、十分に説明されなければならない、そして、同時に2つのシングルトン(NameControllerオブジェクト、ネームサービスオブジェクト)を操作する200件のスレッドは、任意のロック機構を用いることなく、スレッド安全性の問題を生じないことがない限り(可能ではありませんかかわらず、運転の状態)
問題解決策
タイトル、私はここの指示に従って、すなわち、3つのソリューションを提供します
-
同期の変更方法
-
同期コードブロック
- スコープのBeanオブジェクトの変更
次に、各溶液は、それぞれの長所と短所を含め、記載されています
同期の変更方法
メソッドのスレッド安全性の問題を作り出すことができる、それが私たちのほとんどだと思うしそうでなく、最も簡単な解決策である必要があり、我々だけで必要な変更に同期に使用public String changeAndReadName (@RequestParam String name)
増大するように改変同期して、この方法
実際のテストなので、実際に問題を解決していますが、その問題について考えることができれば
我々はテストコードを実行するために戻ってきたとき、彼らが実行する前に、各スレッドの前にすべてのロジックがchangeAndReadName()メソッドを終了するスレッドを待たなければならないため、プログラムの実行効率が大幅に削減されて見つけるが、このロジックは、我々はモデル化するために使用含まれています高いビジネスを時間がかかりThread.sleep(300)
ますが、それは私たちのスレッドセーフとは何の関係もありません
この場合、我々は問題を解決するために第二の方法を使用することができます
同期コードブロック
ロジックの実際のラインは、しばしばこのような状況が発生します。我々は、スレッドセーフコード、高い時間がかかる(例えば、サードパーティのAPIを呼び出す)を使用してコードを確認する必要があり、非常に偶然同じ方法で書かれました
同期コードブロックを使用して、直接ではなく方法を修正することは、より効率的になるだろうし、この場合、
次のように具体的なソリューションのコードは次のとおりです。
@RequestMapping("")
public String changeAndReadName (@RequestParam String name) throws InterruptedException {
System.out.println(Thread.currentThread().getName() + " get new request: " + name);
String result = "";
synchronized (this) {
nameService.setName(name);
result = nameService.getName();
}
Thread.sleep(300);
return result;
}
私たちは、効率は基本的に問題を解決した見つけることができますが、欠点は、おそらく問題のスレッドセーフなコードが表示されているものの私たち自身の良い理解のために必要である、再びテストコードを実行します(実際のラインロジックが非常に複雑になることがあり、これは十分に把握できません)
スコープのBeanオブジェクトの変更
今、非常に不幸なことが起こった、私たちも、コードを取った高依存性状態、だけでなく、効率性を確保する必要があるので、この場合には問題は少量のメモリを犠牲にすることによって解決することができます
各サーバスレッドがセキュリティスレッドを回避するために、無関係互いにによって、ロジックを処理する新しいBeanオブジェクトに対応するように、おそらくアイデアは、Beanオブジェクトのスコープを変更することです
まず第一に、私たちは、スコープのBeanオブジェクトは、以下の表を参照してください何を知っている必要があります
スコープ | 説明 |
---|---|
シングルトン | Beanが、この場合、シングルトンオブジェクトとして定義されるデフォルトの範囲は、オブジェクトのライフサイクルのみである(ただし、スプリング遅延ローディング機構のために、最初はバネIOCコンテナと一致しています作成されます) |
プロトタイプ | Beanは、新しいオブジェクトに注入されるたびに作成されます定義されています |
要求 | 豆の実施形態は、各HTTP要求に対する単一のオブジェクトを作成するために定義され、それはシングルトンオブジェクトを多重化される単一の要求で言うことです |
セッション | Beanは、セッションのライフサイクルの単一の実施形態内のオブジェクトを作成するために定義されています |
応用 | Beanは、単一の多重化された実施形態では、ServletContextオブジェクトのライフサイクルとして定義されます |
WebSocketを | 豆多重化は、シングルトンオブジェクトのライフサイクル用WebSocketのように定義されます |
豆修正のどの範囲:豆の範囲が明確にオブジェクトの後、我々は唯一の問題を検討する必要がありますか?
私はこのケースでは、説明したように、デフォルトは2つのシングルトンBeanオブジェクトを操作している200件のサーバスレッドは、NameControllerで、ネームサービスは(はい、春ブーツで、コントローラのデフォルトはシングルトンオブジェクトです)
それができプロトタイプに直接NameControllerとNameServieではないでしょうか?
プロジェクトは、Struts2のを使用している場合は、何の問題もなくそれを行うが、リクエストがクラスに基づいているので、真剣にSpring MVCの、Struts2の傍受でのパフォーマンスに影響を与える可能性があり、Spring MVCのは法に基づいています
だから我々は、範囲が要求するように設定されている必要がありますNameController解決するためにプロトタイプを作成するためにネームサービスします
次のように特定のオペレーションコード
@RestController
@RequestMapping("name")
@Scope("request")
public class NameController {
}
@Service
@Scope("prototype")
public class NameService {
}
リファレンス
-
https://dzone.com/articles/understanding-spring-reactiveclient-to-server-comm
-
https://dzone.com/articles/understanding-spring-reactive-servlet-async
- https://medium.com/sipios/how-to-make-parallel-calls-in-java-springboot-application-and-how-to-test-them-dcc27318a0cf
元は容易ではない、転載明記してくださいソース
ケース商品コード:GitHubの/ liumapp /ブックレット