この記事は、同時環境で安全でないSimpleDateFormat最適化ケースを使用することにより、誰もがThreadlocalを理解するのに役立ちます。
最近、私は会社のプロジェクトを整理しましたが、次のような多くの悪いことが書かれていることがわかりました。
public class dateutil {private final static simpledateformat sdfyhm = new simpledateFormat( "yyyymmdd"); public同期静的日付parseymdhms(string source){try {return sdfyhm.parse(source); } catch(parseexception e){e.printstacktrace(); new Date()を返します。 }}}まず、分析しましょう。
この時点での関数parseymdhms()は、同期された変更を使用します。つまり、操作はスレッド不使用であるため、同期する必要があります。 Thread-Unsafeは、simpledateformatのparse()方法のみになります。ソースコードを確認してください。 SimpledateFormatにはグローバル変数があります。
保護されたカレンダーカレンダー; date parse(){calendar.clear(); ... //カレンダーおよびその他のカレンダーの日付を設定するためにいくつかの操作を実行します。getTime(); //カレンダーの時間を取得}クリア()操作により、スレッドが不安定になります。
さらに、同期されたキーワードを使用すると、特にマルチスレッドの場合、parseymdhmsメソッドが呼び出されるたびに、同期判断が行われ、同期自体が非常に高価であるため、これは不可解な解決策です。
改善方法
スレッドアンシックは、複数のスレッドによる共有変数の使用によって引き起こされるため、ここではthreadlocal <simpledateformat>を使用して、各スレッドのコピー変数を個別に作成します。最初にコードを提供し、次にこの問題の原因を分析して問題を解決します。
/** *日付ツールクラス(ThreadLocalはSimpleDateFormatを取得するために使用され、その他の方法はCommon-Langによって直接コピーできます) private static logger logger = loggerfactory.getLogger(dationutil.class); public final static string mdhmss = "mmddhhmmsssss"; public final static string ymdhms = "yyymmddhhmmss"; public final static string ymdhms_ = "yyyy-mm-dd hh:mm:ss"; public final static string ymd = "yyyymmdd"; public final static string ymd_ = "yyyy-mm-dd"; public final static string hms = "hhmmss"; / ***マップのキーに基づいて対応するスレッドのSDFインスタンスを取得*マップの@Paramパターンキー* @return This Instance*/ private static simpledateformat getSdf(finalling pattern){threadlocal <simpledateformat> sdfthread = sdfmap.get(パターン); if(sdfthread == null){// sdfmapが複数回になるのを防ぐために再びチェックします。ダブルロックシングルトンの理由は同じ同期(dateutil.class){sdfthread = sdfmap.get(パターン); if(sdfthread == null){logger.debug( "パターンの新しいsdf" + pattern + "to map"); sdfthread = new StreadLocal <SimpleDateFormat>(){@Override Protected SimpledAteFormat initialValue(){logger.debug( "swreet:" + thread.currentthread() + "init pattern:" + pattern);新しいsimpledateFormat(パターン)を返します。 }}; sdfmap.put(pattern、sdfthread); }}} return sdfthread.get(); } / ***指定されたパターンに従って日付を解析する* @param日付は解析される日付* @param日付パターンを指定します。 } try {return getSdf(pattern).parse(date); } catch(parseexception e){e.printstacktrace(); logger.error( "解析された形式は:"+pattern); } nullを返します。 } / ***指定されたパターンによるフォーマット日付* @param日付はフォーマットされる日付* @paramパターンフォーマットを指定します* @return解析済みフォーマット* / public static string formatdate(date date、string pattern){if(date == null){新しいIllegalargumentexception(null "); } else {return getSdf(pattern).format(date); }}}テスト
メインスレッドで1つと、同じパターンを使用して、子スレッドで他の2つを実行します
public static void main(string [] args){dateutil.formatdate(new date()、mdhmss); new shood(() - > {dateutil.formatdate(new date()、mdhmss);})。start(); new shood(() - > {dateutil.formatdate(new date()、mdhmss);})。start(); }ログ分析
パターンmmddhhmmssssssの新しいSDFをMapThread:スレッド[Main、5、Main] init Pattern:mmddhhmmmsssssSthread:thread [thread-0,5、main] init Pattern:mmddhmmsssssSthread:thread [thread-1,5、main] init:mmdhmmssssssssssssssssssssssssssssssssssss
分析します
SDFMAP PUTが1回入力されたことがわかりますが、CODEには3つのスレッドがあったため、SimpleDateFormatは3回新しいものでした。では、なぜこれがあるのですか?
スレッドスレッドごとに、threadlocal.threadlocalMap threadlocalsへのグローバル変数参照があります。 threadlocal.threadlocalMapがあり、スレッドローカルと対応する値を保持します。 1つの写真は千の言葉よりも優れています。構造図は次のとおりです。
したがって、SDFMAPの場合、構造図が変更されます
1。first execute dateutil.formatdate(new date()、mdhmss);
// dateutil.formatdate(new date()、mdhmss)分析private static simpledateformat getsdf(final string pattern){threadlocal <simpledateformat> sdfthread = sdfmap.get(pattern); //取得したsdfthreadはnullです。IFステートメントを入力しますif(sdfthread == null){synchronized(dateutil.class){sdfthread = sdfmap.get(pattern); // sdfthreadはまだnullです。ifステートメントを入力しますif(sdfthread == null){// print logger.debug( "パターンの新しいsdfをput + pattern +" map "); // threadlocalインスタンスを作成し、初期値メソッドsdfthread = new StreadLocal <SimpleDateFormat>(){@Override SimpledAteFormat intialityValue(){logger.debug( "shood:" + thread.currentthread() + "init pattern:" +パターン);新しいsimpledateFormat(パターン)を返します。 }}; // sdfmap sdfmap.put(pattern、sdfthread)に設定します。 }}} return sdfthread.get(); }この時点で、誰かがここで呼び出されないように、誰かが尋ねるかもしれません。
これには、sdfthread.get()の実装が必要です。
public t get(){thread t = thread.currentthread(); ThreadLocalMap Map = getMap(t); if(map!= null){threadlocalmap.entry e = map.getentry(this); if(e!= null){@suppresswarnings( "unchecked")t result =(t)e.value;返品結果; }} return setInitialValue(); }つまり、値が存在しない場合、setInitialValue()メソッドが呼び出されます。
対応するログ印刷。
パターンmmddhhmmsssssの新しいsdfをmapthread:thread [main、5、main] init pattern:mmddhhmmsssssに配置する
2。dateutil.formatdate(new date()、mdhmss)を2回目のスレッドで実行します。
// `dateutil.formatdate(new date()、mdhmss);` private static simpledateformat getsdf(final string pattern){threadlocal <simpledateformat> sdfthread = sdfmap.get(pattern); //ここで取得したsdfthreadはnullではありません、if block if(sdfthread == null){synchronized(dateutil.class){sdfthread = sdfmap.get(pattern); if(sdfthread == null){logger.debug( "パターンの新しいsdf" + pattern + "to map"); sdfthread = new StreadLocal <SimpleDateFormat>(){@Override Protected SimpledAteFormat initialValue(){logger.debug( "swreet:" + thread.currentthread() + "init pattern:" + pattern);新しいsimpledateFormat(パターン)を返します。 }}; sdfmap.put(pattern、sdfthread); }}} // sdfthread.get()を直接呼び出してsdfthread.get(); }分析sdfthread.get()
// `dateutil.formatdate(new date()、mdhmss);` public t get(){thread t = thread.currentthread(); //現在の子スレッドthreadlocalmap map = getMap(t); //子スレッドで取得されたマップはnullです、if block if(map!= null){threadlocalmap.entry e = map.getentry(this); if(e!= null){@suppresswarnings( "unchecked")t result =(t)e.value;返品結果; }} //初期化を直接実行します。つまり、return setInitialValue()を上書きするinitialValue()メソッドを呼び出します。 }対応するログ:
スレッド[スレッド1,5、メイン] initパターン:mmddhhmmsssss
要約します
どのシナリオがthreadlocalの使用に適していますか?誰かがStackoverflowについてかなり良い答えを出しました。
Threadlocal変数をいつ、どのように使用すればよいですか?
可能な(および一般的な)使用の1つは、スレッドセーフではないオブジェクトがあるが、そのオブジェクトへのアクセスの同期を避けたい場合です(私はあなたを見ています、SimpleDateFormat)。代わりに、各スレッドにオブジェクトの独自のインスタンスを与えます。
参照コード:
https://github.com/nl101531/util-demo under javaweb
参考文献:
理解しやすい方法でJava Threadlocalを学びます
SimpledateFormatのスレッド安全性の問題とソリューション
上記はこの記事のすべての内容です。みんなの学習に役立つことを願っています。誰もがwulin.comをもっとサポートすることを願っています。