1概要

この記事では、

java.lang

パッケージからの


ThreadLocal


構文について説明します。これにより、現在のスレッドに対して個別にデータを格納することができます。そして、特別な種類のオブジェクトで単純にラップすることができます。


2

ThreadLocal

API


TheadLocal

コンストラクトを使うと、特定のスレッドによって** アクセスのみ可能なデータを格納できます。

特定のスレッドにバンドルされる

Integer

値が欲しいとします。

ThreadLocal<Integer> threadLocalValue = new ThreadLocal<>();

次に、スレッドからこの値を使用したい場合は、

get()

または

set()

メソッドを呼び出すだけで済みます。簡単に言えば、

ThreadLocal

は、スレッドをキーとして、マップの内部にデータを格納すると考えることができます。

そのため、

threadLocalValue



get()

メソッドを呼び出すと、要求側スレッドの

Integer

値が取得されます。

threadLocalValue.set(1);
Integer result = threadLocalValue.get();


withInitial()

staticメソッドを使用してサプライヤを渡すことで、

ThreadLocal

のインスタンスを構築できます。

ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);


ThreadLocal

から値を削除するには、

remove()

メソッドを呼び出します。

threadLocal.remove();


ThreadLocal

の適切な使用方法を確認するために、まず

ThreadLocal

を使用しない例を見てから、その構文を利用するように例を書き直します。


3ユーザーデータをマップに保存する

与えられたユーザーIDごとにユーザー固有の

Context

データを保存する必要があるプログラムを考えてみましょう:

public class Context {
    private String userName;

    public Context(String userName) {
        this.userName = userName;
    }
}

ユーザーIDごとに1つのスレッドが必要です。

Runnable

インターフェースを実装する

SharedMapWithUserContext

クラスを作成します。


run()メソッドの実装は、指定された

userId



Context

オブジェクトを返す

UserRepository__クラスを介してデータベースを呼び出します。

次に、そのコンテキストを

userId

でキー設定された

ConcurentHashMap

に格納します。

public class SharedMapWithUserContext implements Runnable {

    public static Map<Integer, Context> userContextPerUserId
      = new ConcurrentHashMap<>();
    private Integer userId;
    private UserRepository userRepository = new UserRepository();

    @Override
    public void run() {
        String userName = userRepository.getUserNameForUserId(userId);
        userContextPerUserId.put(userId, new Context(userName));
    }

   //standard constructor
}

2つの異なる

userIds

に対して2つのスレッドを作成して起動し、

userContextPerUserId

マップに2つのエントリがあることを表明することで、コードを簡単にテストできます。

SharedMapWithUserContext firstUser = new SharedMapWithUserContext(1);
SharedMapWithUserContext secondUser = new SharedMapWithUserContext(2);
new Thread(firstUser).start();
new Thread(secondUser).start();

assertEquals(SharedMapWithUserContext.userContextPerUserId.size(), 2);


4

ThreadLocal


へのユーザーデータの格納


ThreadLocal

を使用してユーザーの

Context

インスタンスを格納するように例を書き直すことができます。各スレッドは独自の

ThreadLocal

インスタンスを持ちます。


ThreadLocal

を使用するときは、すべての

ThreadLocal

インスタンスが特定のスレッドに関連付けられているため、非常に注意する必要があります。この例では、それぞれ特定の

userId

に専用のスレッドがあり、このスレッドは私達によって作成されているので、それを完全に制御できます。


run()

メソッドはユーザーコンテキストを取得し、

set()

メソッドを使用して

ThreadLocal

変数に格納します。

public class ThreadLocalWithUserContext implements Runnable {

    private static ThreadLocal<Context> userContext
      = new ThreadLocal<>();
    private Integer userId;
    private UserRepository userRepository = new UserRepository();

    @Override
    public void run() {
        String userName = userRepository.getUserNameForUserId(userId);
        userContext.set(new Context(userName));
        System.out.println("thread context for given userId: "
          + userId + " is: " + userContext.get());
    }

   //standard constructor
}

与えられた

userId

に対してアクションを実行する2つのスレッドを開始することでそれをテストできます。

ThreadLocalWithUserContext firstUser
  = new ThreadLocalWithUserContext(1);
ThreadLocalWithUserContext secondUser
  = new ThreadLocalWithUserContext(2);
new Thread(firstUser).start();
new Thread(secondUser).start();

このコードを実行すると、

ThreadLocal

が特定のスレッドごとに設定されたことが標準出力に表示されます。

thread context for given userId: 1 is: Context{userNameSecret='18a78f8e-24d2-4abf-91d6-79eaa198123f'}
thread context for given userId: 2 is: Context{userNameSecret='e19f6a0a-253e-423e-8b2b-bca1f471ae5c'}

各ユーザーがそれぞれ独自のコンテキストを持っていることがわかります。


5

ThreadLocal



ExecutorService


と一緒に使用しないでください.


ExecutorService

を使用してそれに

Runnable

を送信したい場合、

ThreadLocal

を使用すると不確定な結果が得られます – 指定された

userId

のすべての

Runnable

アクションが実行されるたびに同じスレッドによって処理される保証はない。

そのため、私たちの

ThreadLocal

は異なる

userIdの間で共有されるでしょう。だからこそ

TextorServiceと一緒に

TheadLocal

を使うべきではありません。


6. 結論

このクイック記事では、

ThreadLocal

構文を見ていました。

スレッド間で共有されている

ConcurrentHashMap

を使用して特定の

userIdに関連付けられたコンテキストを格納するロジックを実装しました。次に、

ThreadLocal

を利用して特定の

userId__および特定のスレッドに関連付けられたデータを格納する例を書き直しました。

これらすべての例とコードスニペットの実装はhttps://github.com/eugenp/tutorials/tree/master/core-java-concurrency[GitHubプロジェクト]にあります – これはMavenプロジェクトです。そのままインポートして実行するのは簡単です。