この記事の内容は、Spring におけるシングルトン モードとスレッド セーフの矛盾の解決に関するものです。必要な方は参考にしていただければ幸いです。
Spring フレームワークを使用するときに、マルチスレッドの問題を知らない、または無視している人はどのくらいいるでしょうか?
プログラムを作成したり単体テストを実行したりするときに、マルチスレッド テスト環境をシミュレートするのは簡単ではないため、マルチスレッドの問題が発生しにくいからです。複数のスレッドが同じ Bean を呼び出すと、スレッドの安全性の問題が発生します。 Spring の Bean 作成モードが非シングルトンであれば、このような問題は発生しません。
しかし、潜在的な脆弱性を考慮しないと、それらは目に見えないプログラムのキラーとなり、知らない間に発生します。さらに、プログラムが使用のために配布されるときに、実稼働環境でトリガーするのは通常、非常に面倒です。
Spring はスレッド セーフティの問題を解決するために ThreadLocal を使用します。
一般に、Spring では、ほとんどの Bean がシングルトン スコープとして宣言できるのはステートレス Bean のみであることがわかっています。これは、ステートフル Bean は複数のスレッド間で共有できるため、Spring は ThreadLocal を使用して一部の Bean (RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder など) の非スレッドセーフ状態を処理し、スレッドセーフにするためです。
一般的な Web アプリケーションは、プレゼンテーション層、サービス層、永続化層の 3 つのレベルに分かれており、対応するロジックが異なる層に記述され、下位層はインターフェイスを介して上位層への関数呼び出しを開きます。通常の状況では、リクエストの受信から応答を返すまでのすべてのプログラム呼び出しは同じスレッドに属します。
ThreadLocal は、スレッドの安全性の問題を解決するための良いアイデアであり、スレッドごとに変数の独立したコピーを提供することで、変数への同時アクセスの競合を解決します。多くの場合、ThreadLocal は、同期メカニズムを直接使用してスレッド セーフティの問題を解決するよりも簡単で便利であり、結果として得られるプログラムの同時実行性が高くなります。
コードが配置されているプロセスで複数のスレッドが同時に実行されている場合、これらのスレッドがこのコードを同時に実行する可能性があります。各実行の結果がシングルスレッド実行の結果と同じであり、他の変数の値が予想どおり同じである場合、それはスレッドセーフです。言い換えれば、クラスまたはプログラムによって提供されるインターフェイスは、スレッドのアトミックな操作であるか、複数のスレッド間の切り替えによってインターフェイスの実行結果があいまいになることはありません。これは、同期の問題を考慮する必要がないことを意味します。 スレッドの安全性の問題は、グローバル変数と静的変数によって引き起こされます。
各スレッドのグローバル変数と静的変数に対して読み取り操作のみがあり、書き込み操作がない場合、一般に、複数のスレッドが同時に書き込み操作を実行すると、このグローバル変数はスレッドセーフになります。一般に、スレッドの同期を考慮する必要があります。そうしないと、スレッドの安全性が影響を受ける可能性があります。
1) 読み取り操作のみがあるため、定数は常にスレッドセーフです。
2) 各メソッド呼び出しの前に新しいインスタンスを作成すると、共有リソースにアクセスされないため、スレッドセーフになります。
3) ローカル変数はスレッドセーフです。メソッドが実行されるたびに、共有リソースではない別の空間にローカル変数が作成されるためです。ローカル変数には、メソッド パラメーター変数とメソッド内部変数が含まれます。
ステートフルであるということは、データストレージ機能を持つことを意味します。ステートフル オブジェクト (ステートフル Bean) は、データを保存できるインスタンス変数を持つオブジェクトですが、スレッドセーフではありません。メソッド呼び出し間では状態は保持されません。
ステートレスは操作であるため、データを保存できません。ステートレス オブジェクト (Stateless Bean) は、インスタンス変数を持たないオブジェクトであり、データを保存できず、不変クラスであり、スレッドセーフです。
ステートフル オブジェクト:
ステートレス Bean は不変モードに適しており、このテクノロジはシングルトン モードであるため、インスタンスを共有してパフォーマンスを向上させることができます。ステートフル Bean はマルチスレッド環境では安全ではないため、Prototype プロトタイプ モードが適しています。プロトタイプ: Bean に対するリクエストごとに、新しい Bean インスタンスが作成されます。
Struts2 のデフォルトの実装はプロトタイプ モードです。つまり、リクエストごとに新しい Action インスタンスが生成されるため、スレッド セーフティの問題はありません。アクションのライフサイクルが Spring によって管理されている場合、スコープはプロトタイプ スコープ
Thread safety case
SimpleDateFormat (以下、プロトタイプ スコープ) として設定する必要があることに注意してください。 sdf と呼ばれます) 内部クラスには、sdf.parse(dateStr)、sdf.format(date) などの、この sdf に関連する日付情報を格納するために使用される Calendar オブジェクト参照があります。日付関連の文字列。メソッドのパラメータによって渡される日付などはすべてフレンドです。SDF が静的である場合、複数のスレッドがこの SDF を共有すると同時に、この Calendar 参照も共有します。 sdf.parse() メソッドを観察すると、次の呼び出しが見つかります:
Date parse() { calendar.clear(); // 清理calendar ... // 执行一些操作, 设置 calendar 的日期什么的 calendar.getTime(); // 获取calendar的时间 }
ここで発生する問題は、スレッド A が sdf.parse() を呼び出し、calendar.clear() の後で Calendar.getTime() が実行される前に、スレッド B が sdf.parse() を再度呼び出すことです。今度はスレッド B も sdf.clear() メソッドを実行しました。これにより、スレッド A のカレンダー データがクリアされました (実際には A と B が同時にクリアされました)。または、A が clear() を実行した後、スレッド A が一時停止されました。このとき、B は sdf.parse() の呼び出しを開始し、スムーズに終了します。 このように、A のカレンダーに保存されている日付は、後で B が設定したカレンダーの日付になります。その背後に隠された重要な問題 - ステートレス性: ステートレス メソッドの利点の 1 つは、さまざまな環境で安全に呼び出すことができることです。メソッドがステートフルであるかどうかを測定するには、メソッドがグローバル変数やインスタンス フィールドなど、他のものを変更するかどうかによって決まります。 format メソッドは実行プロセス中に SimpleDateFormat のカレンダー フィールドを変更するため、ステートフルです。
これは、システムを開発および設計するときに次の 3 つの点に注意することも思い出させます。
パブリック クラスを自分で作成する場合は、マルチスレッド呼び出しの結果についてコメントする必要があります。明確に説明します
スレッド環境では、各共有変数変数のスレッド セーフに注意を払う必要があります
クラスとメソッドを設計するときは、それらをシームレスなステータスとして設計するよう最善を尽くす必要があります。
解決策1. 必要に応じて新しいインスタンスを作成します:
手順: 必要に応じて SimpleDateFormat を使用します。 新しいインスタンスを作成します。新しいインスタンスを作成する場所で、スレッド セーフティの問題があるオブジェクトを共有からローカル プライベートに変更すると、マルチスレッドの問題を回避できますが、オブジェクト作成の負担も増加します。通常の状況では、パフォーマンスへの影響はあまり明らかではありません。
2. 同期を使用します。SimpleDateFormat オブジェクトを同期します。public class DateSyncUtil {
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static String formatDate(Date date)throws ParseException{
synchronized(sdf){
return sdf.format(date);
}
}
public static Date parse(String strDate) throws ParseException{
synchronized(sdf){
return sdf.parse(strDate);
}
}
}
public class ConcurrentDateUtil {
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static Date parse(String dateStr) throws ParseException {
return threadLocal.get().parse(dateStr);
}
public static String format(Date date) {
return threadLocal.get().format(date);
}
}
ThreadLocal<DateFormat>(); public static DateFormat getDateFormat() { DateFormat df = threadLocal.get(); if(df==null){ df = new SimpleDateFormat(date_format); threadLocal.set(df); } return df; } public static String formatDate(Date date) throws ParseException { return getDateFormat().format(date); } public static Date parse(String strDate) throws ParseException { return getDateFormat().parse(strDate); } }
注: ThreadLocal を使用すると、共有変数も排他的になり、スレッドの排他性はメソッドと確実に比較されます。排他性により、同時環境でオブジェクトを作成する際のオーバーヘッドを大幅に削減できます。パフォーマンス要件が比較的高い場合は、通常、この方法をお勧めします。
4. JDK を放棄し、他のライブラリの時刻フォーマット クラスを使用します。Apache コモンズで FastDateFormat を使用し、高速でスレッドセーフな SimpleDateFormat を主張しますが、残念ながらそれは可能です。日付のフォーマットのみを実行し、日付文字列を解析することはできません。
Joda-Time クラス ライブラリを使用して時間関連の問題を処理します。
単純なストレス テストを実行します。方法 1 が最も遅く、方法 3 が最も高速ですが、最も遅い方法でも結果は不十分です。パフォーマンスは悪くありません。一般的なシステムの方法 1 と方法 2 は満たされるため、この時点でシステムのボトルネックになることは困難です。単純な観点からは、方法 1 または方法 2 を使用することをお勧めします。必要に応じて少しパフォーマンスの向上を追求する場合は、キャッシュに ThreadLocal を使用する方法 3 の使用を検討できます。
Joda-Time クラス ライブラリは時間処理に最適なので、使用することをお勧めします。
概要記事の冒頭の質問に戻ります。「どれだけの人が、マルチスレッドの問題を知らないか、無視していることがよくありますか?スプリングフレームワーク?」 》
実際、誰でもコードを書くことができます。なぜアーキテクトが書いたコードの効果はあなたのコードと大きく異なるのでしょうか?あなたが考慮していなかったこのような小さな問題は、建築家が考慮したはずです。
アーキテクトはより幅広い知識を持ち、より具体的な状況を見て、さまざまな問題を解決する豊富な経験を持っています。建築家の考え方や習慣を身につけていれば、建築家には程遠いのでしょうか?
以上がSpring でのシングルトン モードとスレッド セーフの間の矛盾を解決するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。