序文
マルチスレッドのスレッドの安全性の問題は、マルチスレッドの操作の順序が適切な同期なしでは予測不可能であるため、微妙かつ予想外です。同じ共有変数にアクセスする複数のスレッドは、特にスレッドの安全性を確保するために共有変数を記述する必要がある場合、同時性の問題に特に発生しやすいものです。
一般的に、ユーザーは、下の図に示すように、共有変数にアクセスするときに適切な同期を実行する必要があります。
同期測定は一般にロックされていることがわかります。これにより、ユーザーはロックを特定の理解を深める必要があり、明らかにユーザーの負担が増加します。変数を作成するとき、各スレッドにアクセスすると、独自のスレッドの変数にアクセスする方法はありますか?実際、Threalocalはこれを行うことができます。 Threadlocalの出現は、上記の問題を解決しないように見えることに注意してください。
ThreadLocalはJDKパッケージに提供されています。スレッドローカル変数を提供します。つまり、Threadlocal変数を作成すると、この変数にアクセスする各スレッドには、変数のローカルコピーがあります。複数のスレッドがこの変数を操作すると、実際に変数が独自のローカルメモリで動作し、それによってスレッドの安全性の問題を回避します。 threadlocal変数を作成した後、
下の図に示すように、各スレッドはローカルメモリに変数をコピーします。
さて、質問について考えてみましょう:Threadlocalの実装原則、そしてThreadlocalは内部的に可変スレッド分離方法としてどのように実装されていますか?
まず、次の図に示すように、threadlocalのクラス図構造を確認する必要があります。
のように
上記のクラス図に見られるように、スレッドクラスにはスレッドロカルと継承することがあります。両方のタイプの変数はThreadLocalMapであり、ThreadLocalMapはカスタマイズされたハッシュマップです。デフォルトでは、各スレッドの両方の変数がnullです。スレッドがThreadlocalセットを呼び出すか、初めてメソッドを取得する場合にのみ作成されます。
実際、各スレッドのローカル変数は、threadlocalインスタンスに保存されていませんが、呼び出しスレッドのthreadlocals変数に保存されます。言い換えれば、タイプのthreadlocalのローカル変数は、特定のスレッドメモリ空間に保存されます。
Threadlocalは実際にはシェルです。値を設定されたメソッドを介して通話スレッドスレッドロカルに値を入れ、保存します。通話スレッドがGetメソッドを呼び出すと、現在のスレッドのスレッドロカル変数から取り出されます。呼び出しスレッドが終了しない場合、ローカル変数は呼び出しスレッドのthreadlocals変数に保存されます。
したがって、ローカル変数を使用する必要がない場合は、ThreadLocal変数の削除方法を呼び出すことにより、現在のスレッドのThreadLocals変数からローカル変数を削除できます。スレッドロカルがマップ構造として設計されている理由を尋ねる人もいますか?各スレッドが複数のスレッドローカル変数に関連付けられることは明らかです。
次に、次のコードに示すように、ThreadLocalのソースコードを入力できます。
主に、次のように、セット、取得、削除の3つのメソッドの実装ロジックを調べます。
最初にセット(t var1)メソッドを見てみましょう
public void set(t var1){//(1)現在のスレッドを取得しますvar2 = thread.currentthread(); //(2)現在のスレッドは、対応するスレッド変数を見つけるためのキーとして使用されます。見つかった場合、threadlocal.threadlocalmap var3 = this.getMap(var2)を設定します。 if(var3!= null){var3.set(this、var1); } else {//(3)最初の呼び出しが作成され、現在のスレッドthis.createmap(var2、var1)の対応するハッシュマップを作成します。 }}上記のコード(1)のように、最初に呼び出しスレッドを取得し、次に現在のスレッドをパラメーターとして使用してGetMap(var2)メソッドを呼び出します。 getMap(スレッドvar2)コードは次のとおりです。
threadlocal.threadlocalMap getMap(thread var1){return var1.threadlocals; }getMap(var2)が行うことは、スレッドの変数スレッドロカルを取得することであり、threadlocal変数がスレッドのメンバー変数にバインドされることがわかります。
getMap(var2)が空ではない場合、値値をthreadlocalsに設定します。つまり、現在の変数値を現在のスレッドのメモリ変数スレッドロカルに配置します。 ThreadLocalsはハッシュマップ構造であり、キーは現在のThreadlocalインスタンスオブジェクトの参照であり、値はSETメソッドに渡される値です。
getMap(var2)が空に戻る場合、セットメソッドが初めて呼ばれ、現在のスレッドのスレッドロカル変数が作成されることを意味します。 createmap(var2、var1)で何が行われているか見てみましょう。
void createmap(thread var1、t var2){var1.threadlocals = new threadlocal.threadlocalmap(this、var2); }あなたが見ることができるのは、現在のスレッドのthreadlocals変数を作成することです。
次に、get()メソッドを見てみましょう。コードは次のとおりです。
public t get(){//(4)現在のスレッドを取得var1 = thread.currentthread(); //(5)threadlocals変数を取得するthreadlocal.threadlocalmap var2 = this.getMap(var1); //(6)threadlocalsがnullでない場合、対応するローカル変数値が返されますif(var3!= null){object var4 = var3.value; var4を返します。 }} //(7)threadlocalsが空の場合、現在のスレッドのthreadlocalsメンバー変数が初期化されます。 this.setInitialValue()を返します。 }コード(4)最初に現在のスレッドインスタンスを取得します。現在のスレッドのthreadlocals変数がnullでない場合、現在のスレッドのローカル変数を直接返します。それ以外の場合、コード(7)を実行して初期化します。SetInitialValue()のコードは次のとおりです。
private t setInitialValue(){//(8)nullオブジェクトvar1 = this.InitialValue();スレッドvar2 = thread.currentthread(); threadlocal.threadlocalmap var3 = this.getMap(var2); //(9)現在のスレッド変数のthreadlocals変数が空でない場合(var3!= null){var3.set(this、var1); //(10)現在のスレッドのthreadlocals変数が空の場合} else {this.createmap(var2、var1); } var1を返します。 }上記のように、現在のスレッドのthreadlocals変数が空でない場合、現在のスレッドのローカル変数値はnullに設定されます。それ以外の場合、CreateMapは現在のスレッドのcreateMap変数に呼び出されます。
次に、void remove()メソッドを見て、コードは次のとおりです。
public void remove(){threadlocal.threadlocalmap var1 = this.getMap(thread.currentthread()); if(var1!= null){var1.remove(this); }}上記のように、現在のスレッドのthreadlocals変数が空でない場合、現在のスレッドのthreadlocalインスタンスで指定されているローカル変数が削除されます。
次に、特定のデモを見てみましょう。コードは次のとおりです。
/*** 2018/6/3にCONGによって作成されました。 */public class threadlocaltest {//(1)print function static void print(string str){//1.1現在のスレッドSystem.out.println(str + ":" + localvariable.get()) //1.2現在のスレッドのローカルメモリのローカルバリイアー変数をクリア//localvariable.remove(); } //(2)threadlocal変数Statrelocal <string> localvariable = new threadlocal <>();を作成します。 public static void main(String [] args){//(3)スレッド1つのスレッドスレッド= new runnable(){public void run(){//3.1スレッドでローカル変数のローカル変数の値を設定します。 System.out.println( "スレッド1" + "のローカル変数を削除した後の結果:" + localvariable.get()}); //(4)2つのスレッドThreadtwo = new Thread(new runnable(){public void run(){//4.1 {//4.1スレッドでローカル変数のローカル変数の値を設定します。スレッド2 " +": " + localvariable.get());}}); //(5)thread threadone.start()を起動します。 threadtwo.start(); }}コード(2)は、threadlocal変数を作成します。
コード(3)と(4)は、それぞれスレッド1と2を作成します。
コード(5)は2つのスレッドを開始します。
スレッド1のコード3.1は、設定されたメソッドを使用してローカルバリブルの値を設定します。この設定は、実際にはスレッド1のローカルメモリのコピーです。このコピーにはスレッド2でアクセスできません。次に、コード3.2が印刷関数を呼び出し、コード1.1はGET関数を介して現在のスレッド(スレッド1)のローカルメモリ(スレッド1)でローカルバリブルの値を取得します。
スレッド2はスレッド1と同様に実行されます。
操作結果は次のとおりです。
ここでは、Threadlocalのメモリリークの問題に注意を払う必要があります
各スレッドには、内部にスレッドロカルという名前のメンバー変数があります。変数タイプはハッシュマップです。重要なのは、定義したthreadlocal変数へのこの参照であり、値は設定時の値です。各スレッドのローカル変数は、スレッド自身のメモリ変数スレッドロカルに保存されます。現在のスレッドが消えない場合、これらのローカル変数はまで保存されます。
したがって、メモリの漏れを引き起こす可能性があるため、使用した後、Threadlocalの削除方法を呼び出して、対応するスレッドのスレッドローカルのローカル変数を削除することを忘れないでください。
コード1.2でコメントを開梱した後、再び実行すると、実行の結果は次のとおりです。
このような質問について考えたことがありますか?子スレッドの親スレッドに設定されたthreadlocal変数の値を取得しますか?
ここでは、親スレッドに設定されたthreadlocal変数の値は、子スレッドで取得できないことを伝えることができます。それで、子スレッドに親スレッドの値にアクセスするようにする方法はありますか? EnselitablethreadLocalは将来的に存在しました。 RedentableThreadLocalはThreadLocalから継承し、子供が親スレッドに設定されたローカル変数にアクセスできる機能を提供します。
まず、次のように読むために、継承の継承クラスのソースコードにアクセスしましょう。
Public Class GenetableThreadLocal <T> extends threadLocal <T> {public EnseletableThreadLocal(){} //(1)Protected T ChildValue(t var1){return var1; } //(2)threadlocalMap getMap(thread var1){return var1.inheritablethreadlocals; }特定}}継承は、継承がthreadlocalを継承し、3つの方法を書き直したことがわかります。上記のコードにマークが付けられています。コード(3)継承はcreatemapメソッドを書き換えていることがわかります。そのため、設定方法が初めて呼び出されたときに、現在のスレッドの継承型rethreadlocals変数のインスタンスがスレッドローカルではなく作成されることがわかります。
コード(2)現在のスレッドの内部マップ変数を取得するためにGETメソッドが呼び出されると、regnetableThreadLocalsが取得されることを知ることができます。
キーポイントは、書き直されたコード(1)が実行されたときにここにあり、子スレッドが親スレッドのローカル変数にアクセスできることを実装する方法です。 Threadで作成されたコードから始まり、threadのデフォルトコンストラクターとthread.javaクラスのコンストラクターは次のとおりです。
/*** 2018/6/3にCONGによって作成されました。 */ publicスレッド(実行可能ターゲット){init(null、ターゲット、 "スレッド - " + nextthreadnum()、0); } private void init(threadgroup g、runnableターゲット、文字列名、long stacksize、accesscontrolcontext acc){// ... //(4)現在のthread parent = currentThread(); // ... //(5)親スレッドの継承可能なrethreadLocals変数がnullでない場合(parent.inheritablethreadlocals!= null)//(6)子スレッドの継承継承の変数を設定します。 this.stacksize = stacksize; tid = nextthreadid(); }スレッドを作成すると、INITメソッドがコンストラクターで呼び出されます。前述のように、継承は継承されたLeadLocalクラスを取得し、SETメソッドは変数継承の継承ReadLeadLocalsを動作させるため、ここでの継承型範囲変数はnullではないため、コード(6)が実行されます。次のように、createinheritedMapメソッドのソースコードを見てみましょう。
Static ThreadLocalMap createInheritedMap(threadLocalMap parentMap){return new SthreadLocalMap(ParentMap); }CreateInheritedMapでは、親スレッドの継承可能なrethreadLocals変数をコンストラクターとして使用して、新しいthreadlocalMap変数を作成し、子スレッドの継承型redreadlocals変数に割り当てることがわかります。次に、ThreadLocalMapのコンストラクターを入力します。ソースコードは次のとおりです。
private threadlocalMap(threadlocalMap parentMap){entry [] parentTable = parentMap.table; int len = parenttable.length; setthreshold(len);表= new entry [len]; for(int j = 0; j <len; j ++){entry e = parenttable [j]; if(e!= null){@suppresswarnings( "unchecked")threadlocal <object> key =(threadlocal <object>)e.get(); if(key!= null){//(7)オーバーライドされたメソッドオブジェクト値= key.childvalue(e.value); // return e.valueエントリc = new entry(key、value); int h = key.threadlocalhashcode&(len -1); while(表[h]!= null)h = nextindex(h、len);表[h] = c;サイズ++; }}}}}}上記のコードが行うことは、親スレッドのexensitablethreadLocalsメンバー変数の値を新しいThreadLocalMapオブジェクトにコピーすることと、コード(7)によって書き換えられたコード(1)も表示されます。
一般的に:継承はコード(2)と(3)を書き換え、特定のスレッドの継承可能な変数変数にローカル変数を保存します。スレッドがセットを介して変数を設定すると、exernitableThreadLocalsインスタンスのメソッドを取得すると、現在のスレッドの継承可能なrethreadLocals変数が作成されます。親スレッドがチャイルドスレッドを作成するとき、
コンストラクターは、親スレッドの継承可能な変数変数のローカル変数をコピーし、子スレッドの継承可能な変数変数にコピーします。
原則がよく理解されたら、次のように、上記のことを確認するために例を見てみましょう。
パッケージcom.hjc;/*** 2018/6/3にCONGによって作成されました。 */public class EnertableThreadLeadLocalTest {//(1)スレッド変数を作成するpublic stathiclocal <string> threadlocal = new threadlocal <string>(); public static void main(string [] args){//(2)Setwetraiable threadlocal.set( "hello java"); //(3)Chrive Child Thread Thread = new Runnable(){public void run(){//(4)子スレッドの値は、スレッド変数system.out.println( "subthread:" + threadlocal.get();}})を出力します。 thread.start(); //(5)メインスレッドは、スレッド変数値System.out.println( "親スレッド:" + threadlocal.get())を出力します。 }}操作結果は次のとおりです。
つまり、同じthreadlocal変数が親スレッドで設定された後、子スレッドでは取得できません。前のセクションの紹介によると、これは通常の現象である必要があります。これは、現在のスレッドがGETメソッドを呼び出すときに現在のスレッドが子スレッドであり、SETメソッドが呼び出され、スレッド変数がメインスレッドであるためです。 2つは異なるスレッドであり、当然、子供のスレッドはアクセス時にnullを返します。
それで、子スレッドに親スレッドの値にアクセスするようにする方法はありますか?答えはイエスなので、上記の原則によって分析された継承の継承を使用してください。
上記の例のコード(1)を次のように変更します。
//(1)スレッド変数を作成するpublic staticlocal <string> threadlocal = new enternitablethreadLocal <String>();
操作結果は次のとおりです。
スレッド変数値は、通常、子スレッドから取得できることがわかります。それでは、どのような状況下で、子どもは親スレッドから糸局所変数を取得する必要がありますか?
ユーザーログイン情報を保存するThreadlocal変数など、非常に多くの状況があります。サブスレッドもユーザーログイン情報を使用する必要がある可能性が非常に高いです。たとえば、一部のミドルウェアでは、統一された追跡IDを使用して、呼び出しリンク全体を記録する必要があります。
スプリングリクエストスコープスコープビーンでのthreadlocalの使用
春にXMLでBeanを構成する場合、Scope属性を指定して、Beanの範囲をSingleton、Prototype、Request、Sessionなどに構成できることを知っています。Beanの範囲の実装原則は、Threadlocalを使用して実装されています。スプリングコンテナに豆をWebの範囲にしたい場合は、
Beanレベルを構成するために必要な対応するスコープ属性に加えて、次のようにWeb.xmlで構成する必要があります。
<リスナー> <リスナークラス> org.springframework.web.context.request.RequestContextListener </ristener-class> </ristener>
ここでは、主にRequestContextListenerの2つの方法を見ています。
public void requestInitialized(servletrequestevent requestevent)
そして
public void requestDestroyed(servletRequestevent requestEvent)
Webリクエストが発生すると、RequestInitializedメソッドが実行されます。
public void requestInitialized(servletRequestevent requestEvent){..... omit httpservletrequest =(httpservletrequest)requestevent.getServletrequest(); ServleTRequestAttributes属性= new ServletRequestattributes(request); request.setattribute(request_attributes_attribute、属性); localecontextholder.setlocale(request.getLocale()); // threadlocalに属性を設定しますrequestcontextholder.setRequestattributes(属性); } public static void setRequestattributes(requestattributes属性){setRequestattributes(属性、false); } public static void setRequestattributes(requestattributes属性、boolean継承可能){if(attributes == null){resetrequestattributes(); } else {// default enternitable = false if(enternitable){enternitablerequestattributesholder.set(属性); requestattributesholder.remove(); } else {requestAttributesholder.set(属性); EnturitableRequestattributesholder.Remove(); }}}上記のソースコードを見ることができます。デフォルトの継承可能はfalseであるため、属性値はrequestattributeshoderに配置され、その定義は次のとおりです。
private static final threadlocal <requestattributes> requestattributesholder = new namedthreadlocal <requestattributes>( "request actributes"); private static final threadlocal <requestattributes> exensitablerequestattributesholder = new namedinheritablethreadlocal <requestattributes>( "request context");
その中でも、名前を付けたThreadLocal <t>はthreadlocal <t>を拡張するため、継承可能ではありません。
その中でも、名前を付けたThreadLocal <t>はthreadlocal <t>を拡張するため、継承可能ではありません。
namehheritablethreadlocal <t> extense extensiveTheReadLeadLocal <T>を拡張するため、継承があるため、requestContextholderに配置された属性値はデフォルトで子スレッドで取得できません。
リクエストが終了すると、RequestDestroyedメソッドが呼び出され、ソースコードは次のとおりです。
public void requestDestroyed(servletRequestEvent requestEvent){servletRequestattributes attributes =(servletRequestattributes)requestEvent.getServletRequest()。 ServletRequestattributes threadattributes =(servletRequestattributes)requestContextholder.getRequestattributes(); if(threadattributes!= null){//初期リクエストスレッドの現在のスレッドのスレッドをクリアする可能性が非常に高い(属性== null){astributes = threadattributes; } //要求が終了したら、現在のスレッドのスレッド変数をクリアします。 localecontextholder.resetlocalecontext(); RequestContextholder.resetRequestattributes(); } if(attributes!= null){attributes.requestCompleted(); }}次に、タイミングチャートからのWeb要求呼び出しのロジックを見てみましょう。
つまり、Tomcatでコンテキスト(特定のアプリケーション)を処理する前にWebリクエストを開始するたびに、RequestContextholderプロパティはホストの一致後に設定され、RequestAttributesholderが空でなく、リクエストの最後にクリアされます。
したがって、デフォルトでは、RequestContexTholderに配置された属性の子スレッドにアクセスできず、SpringLocalを使用してSpringのリクエスト範囲内の豆が実装されます。
次に、シミュレーション要求の例が実行されます。コードは次のとおりです。
web.xml構成は次のとおりです。
それはリクエスト範囲であるため、Webプロジェクトである必要があり、RequestContextListenerをWeb.xmlに設定する必要があります。
<リスナー> <リスナークラス> org.springframework.web.context.request.RequestContextListener </ristener-class> </ristener>
次に、リクエストスコープビーンをIOCコンテナに注入します。コードは次のとおりです。
<bean id = "requestbean" scope = "request"> <プロパティ名= "name" value = "hjc" /> <aop:scoped-proxy /> < /bean>
テストコードは次のとおりです。
@webresource( "/testservice")パブリッククラスtestrpc {@autowired private requestbean requestinfo; @resourcemapping( "test")public ActionResult test(errorcontext context){actions result = new ActionResult(); pvginfo.setname( "hjc");文字列名= requestInfo.getName(); result.setValue(name);返品結果; }}上記のように、最初にrequestContextListenerをWeb.xmlに設定し、RequestBeanインスタンスをリクエストスコープでIOCコンテナに挿入します。最後に、RequestBeanインスタンスがtestRPCに注入されます。メソッドテストは、最初にrequestInfoメソッドSetNameを呼び出して名前属性を設定し、名前属性を取得して返すようにします。
ここで、RequestInfoオブジェクトがSingletonの場合、複数のスレッドが同時にテスト方法を呼び出すと、各スレッドはセットゲット操作です。この操作は原子ではなく、糸の安全性の問題を引き起こします。ここで宣言されているスコープはリクエストレベルであり、各スレッドにはrequestInfoのローカル変数があります。
上記のサンプルメソッドリクエストのタイミングチャートは次のとおりです。
テストを呼び出すときに何が起こるかに焦点を合わせる必要があります。
実際、以前に作成されたRequestInfoは、CGLIBによってプロキシ化された後(興味がある場合はScopedProxyFactoryBeanおよびその他のタイプを勉強できます)。したがって、SetNameまたはGetNameに電話すると、DynamicAdvisedInterceptorによって傍受されます。インターセプターは、最終的にrequestScopeのGETメソッドを呼び出して、現在のスレッドが保持しているローカル変数を取得します。
キーはここにあります。次のように、requestscope取得方法のソースコードを確認する必要があります。
public Object(string name、objectactory objectfactory){requestattributes attributes = requestcontextholder.currentrequestattributes(); //(1)object scopedobject = attributes.getattribute(name、getScope()); if(scopedObject == null){scopedObject = objectFactory.getObject(); //(2)attributes.setattribute(name、scopedObject、getScope(); //(3)} return scopedObject; }リクエストが開始されると、RequestContextListener.RequestContextListener.setRequestattributessでRequestContextListener.RequestInitializedを呼び出すことにより、RequestAttributesholderが設定されることがわかります。
次に、リクエストがTESTRPCのテスト方法にルーティングされた後、SETNAMEメソッドがテスト方法で最初に呼び出されるとき、RequestScope.get()メソッドが最終的に呼び出されます。 GETメソッド(1)のコードは、requestContextListener.requestInitializedを介して設定されたスレッドローカル変数requestAttributesholderによって保存された属性セットの値を取得します。
次に、属性セットにrequestInfoという名前の属性があるかどうかを確認します。最初の呼び出しであるため、存在しないため、コードが実行され(2)、SpringがRequestInfoオブジェクトを作成し、属性セット属性に設定します。つまり、現在のリクエストスレッドのローカルメモリに保存されます。次に、作成されたオブジェクトを返し、作成されたオブジェクトのsetNameを呼び出します。
最後に、getNameメソッドはテストメソッドで呼び出され、requestscope.get()メソッドが呼び出されます。 GETメソッド(1)のコードは、RequestContextListener.RequestInitializedを介して[RequestContextListener.RequestInitializedを介して設定されたスレッド]ローカル変数requestAttributesを取得し、属性セットにrequestInfoという名前の属性があるかどうかを確認します。
requestInfoという名前のBeanがSetnameを初めてsetNameで呼び出されるとthreadlocal変数に設定されているため、同じスレッドがsetNameとgetNameで呼び出されたため、Calling SetNameがここで直接返されるときに作成されたRequestInfoオブジェクトは、GetNameメソッドが呼び出されます。
これまでのところ、Threadlocalの実装原則を理解しており、Threadlocalは継承をサポートしていないことを指摘しています。次に、StreadLocalが継承をサポートしていない機能を継承する方法を継承する方法をすぐに説明しました。最後に、スプリングフレームワークでthreadlocalを使用する方法を簡単に紹介し、reqeust範囲の豆を実装しました。
要約します
上記は、この記事のコンテンツ全体です。この記事の内容には、すべての人の研究や仕事に特定の参照値があることを願っています。ご質問がある場合は、メッセージを残してコミュニケーションをとることができます。 wulin.comへのご支援ありがとうございます。