java  におけるThreadLocalの実例分析

7255 ワード

java  におけるThreadLocalの実例分析
概念的に理解すると、threadlocalは変数を複数のスレッドで相互に分離させてスレッドの安全を実現し、threadlocalパッケージの変数は最終的にそれぞれのスレッドに専用であり、スレッド間は互いに独立しており、1つの具体的な実装で説明される。

public interface Consumer {
  int consume();
}
public class ComsumeThread implements Runnable {

  private Consumer consumer;

  public ComsumeThread(Consumer consumer) {
    this.consumer = consumer;
  }

  @Override
  public void run() {
    for(int i=0;i<10;i++){
      System.out.println(Thread.currentThread().getName()+" After Consume left:"+consumer.consume());
    }

  }
}
public class ConsumeClientA implements Consumer {

  private static int leftNum = 30;

  @Override
  public int consume() {
    int orgLeftNum = leftNum;
    Random random = new Random(System.currentTimeMillis());
    try {
      Thread.sleep(random.nextInt(3));
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    orgLeftNum = orgLeftNum -1;
    leftNum = orgLeftNum;
    return leftNum;
  }

  public static void main(String[] args){
    Consumer consumer = new ConsumeClientA();
    Thread thread1 = new Thread(new ComsumeThread(consumer));
    Thread thread2 = new Thread(new ComsumeThread(consumer));
    Thread thread3 = new Thread(new ComsumeThread(consumer));

    thread1.start();
    thread2.start();
    thread3.start();
  }
}

ConsmeClientAはスレッドの安全処理を行っていません。結果は以下の通りです。

Thread-2 After Consume left:29
Thread-1 After Consume left:29
Thread-3 After Consume left:29
Thread-2 After Consume left:28
Thread-1 After Consume left:28
Thread-3 After Consume left:28
Thread-2 After Consume left:27
Thread-1 After Consume left:27
Thread-2 After Consume left:26
Thread-3 After Consume left:27
Thread-1 After Consume left:25
Thread-2 After Consume left:25
Thread-3 After Consume left:25
Thread-1 After Consume left:24
Thread-2 After Consume left:24
Thread-3 After Consume left:24
Thread-1 After Consume left:23
Thread-2 After Consume left:23
Thread-3 After Consume left:23
Thread-1 After Consume left:22
Thread-2 After Consume left:22
Thread-3 After Consume left:22
Thread-1 After Consume left:21
Thread-2 After Consume left:21
Thread-3 After Consume left:21
Thread-1 After Consume left:20
Thread-2 After Consume left:20
Thread-3 After Consume left:20
Thread-1 After Consume left:19
Thread-3 After Consume left:18
threadlocal処理を追加し、スレッドごとに独立して以下のように実現します。

public class ConsumeClientB implements Consumer {
  private ThreadLocal<Integer> leftNumThreadLocal = new ThreadLocal<Integer>(){
    @Override
    protected Integer initialValue() {
      return 30;
    }
  };

  @Override
  public int consume() {
    int orgLeftNum = leftNumThreadLocal.get();
    Random random = new Random(System.currentTimeMillis());
    try {
      Thread.sleep(random.nextInt(3));
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    orgLeftNum = orgLeftNum -1;
    leftNumThreadLocal.set(orgLeftNum);
    return leftNumThreadLocal.get();
  }

  public static void main(String[] args){
    Consumer consumer = new ConsumeClientB();
    Thread thread1 = new Thread(new ComsumeThread(consumer));
    Thread thread2 = new Thread(new ComsumeThread(consumer));
    Thread thread3 = new Thread(new ComsumeThread(consumer));

    thread1.start();
    thread2.start();
    thread3.start();
  }
}

運転の結果は以下の通りです。

Thread-1 After Consume left:29
Thread-3 After Consume left:29
Thread-2 After Consume left:29
Thread-1 After Consume left:28
Thread-3 After Consume left:28
Thread-2 After Consume left:28
Thread-1 After Consume left:27
Thread-3 After Consume left:27
Thread-2 After Consume left:27
Thread-1 After Consume left:26
Thread-3 After Consume left:26
Thread-2 After Consume left:26
Thread-1 After Consume left:25
Thread-3 After Consume left:25
Thread-2 After Consume left:25
Thread-1 After Consume left:24
Thread-3 After Consume left:24
Thread-2 After Consume left:24
Thread-1 After Consume left:23
Thread-3 After Consume left:23
Thread-2 After Consume left:23
Thread-1 After Consume left:22
Thread-3 After Consume left:22
Thread-2 After Consume left:22
Thread-1 After Consume left:21
Thread-3 After Consume left:21
Thread-2 After Consume left:21
Thread-1 After Consume left:20
Thread-3 After Consume left:20
Thread-2 After Consume left:20
各スレッドは独自の変数を持ち、互いに隔離してスレッドの安全を実現する。
では、ThreadLocalはどうやってこのスレッドを隔離するスレッドの安全を実現しますか?
ThreadLocalソースから見ることができます。本当にスレッド分離を実現して、スレッドとリンクしています。実はThreadLocal.ThreadLocarMapという実現カテゴリです。最も顕著な表現はThreadのソースコードのこのような変数申明がThreadLocal.ThreadLocal MapとThreadの関係を説明しました。

ThreadLocal.ThreadLocalMap threadLocals, inheritableThreadLocals;
Thread類はthreadLocarsの対象を含むもので、ThreadLocalの具体的な実現は提供されたget、setなどのインターフェースによって、現在のthreadのthreadLocars変数に関連した操作を行うもので、例えばget操作コードは以下の通りです。

  public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
      ThreadLocalMap.Entry e = map.getEntry(this);
      if (e != null)
        return (T)e.value;
    }
    return setInitialValue();
  }

  ThreadLocal.ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
  }

getMap()の方法は、現在のthreadから対応するthreadLocars変数を取得し、このThreadLocal.ThreadLocal MapタイプのthreadLocars変数から対応スレッドにおけるThreadLocalオブジェクトに対応する変数値を取得することである。
セット方法の操作も同じです。

  public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocal.ThreadLocalMap map = getMap(t);
    if(map != null) {
      map.set(this, value);
    } else {
      this.createMap(t, value);
    }

  }

  void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
  }


static class Entry extends WeakReference<ThreadLocal> {
      Object value;

      Entry(ThreadLocal var1, Object var2) {
        super(var1);
        this.value = var2;
      }
    }

ThreadLocalMapには内部的なEntryの配列があり、EntryはWeakReferenceを継承して実現されます。Weak Referenceの利点は保存先参照であり、対象がGCに回収されても邪魔されず、スレッドが実行されたときにEntryパッケージの変数に干渉されません。
また、ThreadLocal MapのkeyはThreadLocalであるため、一つのThreadLocalの対象は一つのThreadオブジェクトの中に一つのThreadLocalのvalueしか保存できません。
このように、ThreadLocalの実現はThreadLocal MapにThreadを保存する対象がkeyであり、変数がvalueのmap構造であるという人が多くいますが、実は間違っています。
読んでくれてありがとうございます。みなさんのご協力をお願いします。ありがとうございます。