ThreadLocalを使ってみる

JavaのWEBアプリで、よくデータソースとか、セッション情報をThreadLocalに設定する場合があります。
スレッド毎に独立している領域で操作を行えるので、便利です。
例えば、WEBアプリでデータ更新処理を行う場合に、データベースに更新者IDも記録するとします。
この場合WEB層でセッションからログインしているユーザーのIDを取得し、DAO層まで引数で渡したりしちゃいます。
これを、WEB層でThreadLocalに設定することで、引数で渡さずに更新処理のリクエスト(スレッド)が終了する間、どの層でもユーザーIDを取得できるようになります。

ちょっと見てみましょう。

// メインクラス
public class Test {
    
    public static void main(String[] args) {
        new Test().exe();
    }
    
    private void exe() {
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();
        thread1.start();
        thread2.start();
    }
    
    class MyThread extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 500; i++) {
                Target.test(Thread.currentThread().getName() + " : ");
            }
        }
    }
}
// 実行対象クラス
public class Target {

    static Integer cnt = null;
    
    public static void test(String name) {
        if (cnt == null) {
            cnt = new Integer(0);
            System.out.println(Thread.currentThread().getName() + "初期化しました。");
        }
        System.out.println(name + cnt++);
    }
}

以下のように表示されました。

Thread-0初期化しました。
Thread-1初期化しました。
Thread-0 : 0
Thread-1 : 0
Thread-0 : 1
Thread-0 : 3
Thread-1 : 2
・
・
Thread-0 : 996
Thread-0 : 997
Thread-0 : 998

2つのスレッドがTargetのcntを更新しあってます。


それと、いまあんまり関係ないけど、よくシングルトンの作りの問題で言われるように、testメソッドのif文に2つのスレッドが入り込んでいますね(Thread-0、Thread-1の両方で初期化の表示とか、同じ0を表示している)。結構試したんですが、5割以上の確率で入り込みました。


これを、ThreadLocalを使って、スレッド毎に独立した処理にしましょう。

// 実行対象クラス
public class Target {
    
    static ThreadLocal<Integer> cnt = new ThreadLocal<Integer>();
    
    public static void test(String name) {
        if (cnt.get() == null) {
            cnt.set(new Integer(0));
            System.out.println(Thread.currentThread().getName() + "初期化しました。");
        }
        System.out.println(name + cnt.get());
        cnt.set(cnt.get() + 1);
    }
}

以下のように表示されました。

Thread-0初期化しました。
Thread-0 : 0
Thread-1初期化しました。
Thread-1 : 0
Thread-0 : 1
Thread-1 : 1
Thread-1 : 2
Thread-0 : 2
Thread-0 : 3
Thread-0 : 4
・
・
Thread-1 : 496
Thread-0 : 499
Thread-1 : 497
Thread-1 : 498
Thread-1 : 499

このように、スレッド2つで独立して初期化処理が通り、cntがそれぞれ独立しています。
このtestメソッドのif文は必ずスレッドの数分通ります。
ということで、一度ThreadLocalに設定してしまえば、同じスレッド(=同じリクエスト)上ならどこからでも同一のインスタンスを扱うことができるということですねー。