Java ThreadLocalの例

JavaのThreadLocalは、スレッドローカル変数を作成するために使用されます。オブジェクトのすべてのスレッドが変数を共有するため、変数はスレッドセーフではありません。スレッドセーフを確保するためには同期を使用することもできますが、同期を避けたい場合はThreadLocal変数を使用することができます。

Java ThreadLocal

各スレッドには独自のThreadLocal変数があり、それぞれのスレッドでデフォルト値を取得したり値を変更したりするために、get()メソッドやset()メソッドを使用することができます。ThreadLocalインスタンスは、スレッドと関連付けたい状態を持つクラスのprivate staticフィールドとして通常使用されます。

Java ThreadLocalの例

以下は、JavaプログラムでThreadLocalの使用を示し、各スレッドが独自のThreadLocal変数のコピーを持っていることを証明する簡単な例です。ThreadLocalExample.java

package com.journaldev.threads;

import java.text.SimpleDateFormat;
import java.util.Random;

public class ThreadLocalExample implements Runnable{

    // SimpleDateFormatはスレッドセーフではないため、各スレッドに一つずつ与える
    private static final ThreadLocal formatter = new ThreadLocal(){
        @Override
        protected SimpleDateFormat initialValue()
        {
            return new SimpleDateFormat("yyyyMMdd HHmm");
        }
    };
    
    public static void main(String[] args) throws InterruptedException {
        ThreadLocalExample obj = new ThreadLocalExample();
        for(int i=0 ; i<10; i++){
            Thread t = new Thread(obj, ""+i);
            Thread.sleep(new Random().nextInt(1000));
            t.start();
        }
    }

    @Override
    public void run() {
        System.out.println("Thread Name= "+Thread.currentThread().getName()+" default Formatter = "+formatter.get().toPattern());
        try {
            Thread.sleep(new Random().nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //スレッドごとにここでフォーマットパターンを変更しても、他のスレッドには反映されない
        formatter.set(new SimpleDateFormat());
        
        System.out.println("Thread Name= "+Thread.currentThread().getName()+" formatter = "+formatter.get().toPattern());
    }

}

上記のJava ThreadLocalの例プログラムの出力は次のとおりです:

Thread Name= 0 default Formatter = yyyyMMdd HHmm
Thread Name= 1 default Formatter = yyyyMMdd HHmm
Thread Name= 0 formatter = M/d/yy h:mm a
Thread Name= 2 default Formatter = yyyyMMdd HHmm
Thread Name= 1 formatter = M/d/yy h:mm a
Thread Name= 3 default Formatter = yyyyMMdd HHmm
Thread Name= 4 default Formatter = yyyyMMdd HHmm
Thread Name= 4 formatter = M/d/yy h:mm a
Thread Name= 5 default Formatter = yyyyMMdd HHmm
Thread Name= 2 formatter = M/d/yy h:mm a
Thread Name= 3 formatter = M/d/yy h:mm a
Thread Name= 6 default Formatter = yyyyMMdd HHmm
Thread Name= 5 formatter = M/d/yy h:mm a
Thread Name= 6 formatter = M/d/yy h:mm a
Thread Name= 7 default Formatter = yyyyMMdd HHmm
Thread Name= 8 default Formatter = yyyyMMdd HHmm
Thread Name= 8 formatter = M/d/yy h:mm a
Thread Name= 7 formatter = M/d/yy h:mm a
Thread Name= 9 default Formatter = yyyyMMdd HHmm
Thread Name= 9 formatter = M/d/yy h:mm a

出力からわかるように、Thread-0はformatterの値を変更しましたが、Thread-2のデフォルトのformatterは初期化された値と同じです。他のスレッドでも同じパターンが見られます。アップデート:Java 8では、ThreadLocalクラスが新しいメソッドwithInitial()を持ち、これにはSupplier関数インターフェースを引数として指定できます。そのため、ラムダ式を使用して簡単にThreadLocalインスタンスを作成することができます。例えば、上記のformatterのThreadLocal変数は以下のように1行で定義することができます:

private static final ThreadLocal<SimpleDateFormat> formatter = 
	ThreadLocal.<SimpleDateFormat>withInitial
	(() -> {return new SimpleDateFormat("yyyyMMdd HHmm");});

Java 8の機能について初めての方は、Java 8の機能Java 8の関数インターフェースをチェックしてください。これでJavaプログラミングにおけるThreadLocalについては以上です。参考:APIドキュメント

Source:
https://www.digitalocean.com/community/tutorials/java-threadlocal-example