2016-05-19 1 views
4

以下のシングルスコープスコープのサービスクラスでは、クラス内のすべてのメソッドは、Service.doA()が呼び出されたときにわかっているユーザーコンテキストを必要とします。メソッド間で情報を渡す代わりに、これらの値をTheadLocalに格納することを考えていました。このアプローチに関する2つの質問があります:Spring SingletonスコープサービスでThreadLocalを使用する際の質問

1)以下の実装ではThreadLocalを正しく使用していますか?つまり、スレッドセーフで、正しい値がThreadLocalに読み書きされますか?

2)メモリリークを防ぐためにThreadLocal userInfoを明示的にクリーンアップする必要がありますか?それはゴミ収集されますか?

@Service 
public class Service { 
    private static final ThreadLocal<UserInfo> userInfo = new ThreadLocal<>(); 

    public void doA() { 
     // finds user info 
     userInfo.set(new UserInfo(userId, name)); 
     doB(); 
     doC(); 
    } 

    private void doB() { 
     // needs user info 
     UserInfo userInfo = userInfo.get(); 
    } 

    private void doC() { 
     // needs user info 
     UserInfo userInfo = userInfo.get(); 
    } 
} 
+0

シングルトンBeanを使用している場合は、フィールドをインスタンスフィールドにします。 –

+0

@SotiriosDelimanolisしかし、UserInfoインスタンスのフィールドはスレッドセーフではありませんか? – Glide

+0

インスタンスフィールドであるかクラスフィールドであるかにかかわらず、スレッドの安全性については何も変わりません。また、 'UserInfo'フィールドでもなく、' ThreadLocal'フィールドです。 'ThreadLocal'はスレッドセーフです。フィールドは「ファイナル」になります。 –

答えて

5

1)のサンプルコードは、あなたがあなたのようにThreadLocalを参照する静的変数に同じ名前を使用しているDOBとDOCで名前の衝突を除き、okですあなたがThreadLocalから引っ張ったものを保持するローカル変数用です。

2)ThreadLocalに保存するオブジェクトは、明示的に削除されるまでそのスレッドに接続されたままです。サービスがサーブレットコンテナ内で実行される場合、たとえばリクエストが完了すると、そのスレッドはプールに戻ります。スレッドのThreadLocal変数の内容をクリーンアップしていない場合、そのデータはスレッドが次に割り当てられる要求にかかわらず付随します。各スレッドはGCルートであり、スレッドに添付されたスレッドローカル変数は、スレッドが終了するまでガベージコレクションされません。 the API doc

各スレッドは、スレッドが生きていて、ThreadLocalインスタンスがアクセス可能であれば、スレッドローカル変数のコピーへの暗黙の参照を保持します。スレッドがなくなると、スレッドローカルインスタンスのすべてのコピーはガベージコレクションの対象になります(これらのコピーへの他の参照が存在しない限り)。

コンテキスト情報が1つのサービスの範囲に限定されている場合は、ThreadLocalを使用するのではなく、パラメータを使用して情報を渡す方がよいでしょう。 ThreadLocalは、異なるサービス間または異なるレイヤー間で情報を利用する必要がある場合に使用します。コードが1つのサービスでのみ使用される場合は、コードが複雑すぎるようです。異なる異種のオブジェクトに対するAOPのアドバイスによって使用されるデータがある場合、そのデータをスレッドローカルに置くことは有効な使用法になります。

通常、クリーンアップを実行するには、スレッドがスレッドプールに返される前にスレッドローカル変数を削除できるサーブレットフィルタなど、現在の処理でスレッドが完了した時点を特定します。 threadlocalオブジェクトを挿入する場所は、どこを掃除しているところにもないため、try-finallyブロックは使用しません。

0

これは間違いなく使用後にクリーンアップする必要があります。 ThreadLocalsは、クラスローダーの漏洩によるヒープメモリーとpermgen/metaspaceメモリーの両方を非常に簡単にリークします。あなたのケースでは最良の方法は、次のようになります。

public void doA() { 
    // finds user info 
    userInfo.set(new UserInfo(userId, name)); 
    try { 
    doB(); 
    doC(); 
    } finally { 
    userInfo.remove() 
    } 
} 
1

ので、あなたはあなたが起こるものは何でも、それをクリーンアップすることを確認する必要がありThreadLocalを使用します。オブジェクトが対象であるため、値がGCによって収集することができないとして、それが何らかの形でメモリリークを作成

  1. オブジェクトに直接的または間接的にハード参照を持つオブジェクトがもう存在しない場合に限り、GCを実行します。たとえば、ThreadLocalインスタンスでは、内部でThreadLocalMapが使用されていますが、このハードリファレンスを取り除く唯一の方法は、ThreadLocalMapから値を削除するため、ThreadLocalMap#remove()を呼び出すことです。あなたの値をGCに適格にするもう1つの方法は、ThreadLocalインスタンス自体がGCに適格である場合ですが、ここではそれはクラスServiceの定数であるため、私たちが望むGCには適格ではありませんあなたの場合。したがって、唯一の期待される方法はThreadLocalMap#remove()です。
  2. あなたThreadLocalを使用してスレッドがあなたのThreadLocalが適切にクリーンアップされていないので、もしスレッドが別の要求のために再利用されるようにthread poolの一部であり、ほとんどの時間、スレッドは再利用しますので、それを見つけるのは難しいバグを作成し、 ThreadLocalに格納されているオブジェクトのインスタンスは、新しい要求に関連していない可能性もあり、複雑なバグが発生します。例えば、ここではThreadLocalがクリーンアップされていないという理由だけで、別のユーザーの結果を得ることができます。

だからパターンは以下の通りです:スレッドの安全性について

try { 
    userInfo.set(new UserInfo(userId, name)); 
    // Some code here 
} finally { 
    // Clean up your thread local whatever happens 
    userInfo.remove(); 
} 

、各スレッドがそうUserInfoなしインスタンスの独自のインスタンスを使用しますので、UserInfoは、スレッドセーフでない場合であっても当然のスレッドで安全ですThreadLocalに格納されているUserInfoの値は、ThreadLocalの値が何らかの形で現在のスレッドにスコープされているため、複数のスレッドによってアクセスまたは変更されます。

+0

あなたの徹底的な対応に感謝します。 2つのフォローアップの質問。 1)どのように 'ThreadLocal'によって保存されたオブジェクトはGCでクリーンアップできませんか? 2)私の例で 'TheLocal.remove()'が呼び出されなかったとすれば、コードはスレッドセーフではないと言っていますか? (つまりdoB()で別のuserInfoを取得している可能性があります) – Glide

+1

#2のために#1の答えを改善しました。TheadLocal.remove()を呼び出さないと、上記の問題に直面しますTheadLocalクラスはスレッドセーフであり、remove()を呼び出さなくてもスレッドセーフなままです。 –

関連する問題