同時プログラミング(9)threadLocalの使い方

5548 ワード

この章では、ThreadLocalの使用について説明します.主に以下の章に分かれています.
.1)ThreadLocalとは何ですか.
ThreadLocalを使用して変数を維持する場合、ThreadLocalは、その変数を使用するスレッドごとに独立した変数のコピーを提供するので、各スレッドは、他のスレッドに対応するコピーに影響を与えることなく、独立して自分のコピーを変更することができます.
Threadではなくthreadlocalvariable(スレッドローカル変数、スレッドローカル変数とも呼ばれる)です.
.2)ThreadLocalの使用
何も言わないで、まず例を見てみましょう.
Personオブジェクトの作成
public class Person {
	
	private int age;
	
	private String name;

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
}
ThreadLocal処理:
public class PersonManager {

	private static ThreadLocal<Person> personContor = new ThreadLocal<Person>() {
		@Override
		protected Person initialValue() {
			Person p = new Person();
			p.setAge(19);
			p.setName("xgj");
			return p;
		}
	};
	
	private static Person getPerson(){
		return personContor.get();
	}
	
	@SuppressWarnings("unused")
	private void setPerson(Person p){
		personContor.set(p);
	}
	
	public static void main(String[] args) throws InterruptedException {
		Thread p1=new Thread(){

			@Override
			public void run() {
				Person p=PersonManager.getPerson();
				p.setAge(20);
				System.out.println("Person["+p.getAge()+","+p.getName()+"]");
			}
			
		};
		Thread p2=new Thread(){

			@Override
			public void run() {
				Person p=PersonManager.getPerson();
				p.setAge(21);
				System.out.println("Person["+p.getAge()+","+p.getName()+"]");
			}
			
		};
		Thread p3=new Thread(){

			@Override
			public void run() {
				Person p=PersonManager.getPerson();
				System.out.println("Person["+p.getAge()+","+p.getName()+"]");
			}
			
		};
		
		System.out.println(PersonManager.getPerson().getAge());
		p1.start();
		p2.start();
		p3.start();
		p1.join();
		p2.join();
		p3.join();
		System.out.println(PersonManager.getPerson().getAge());
	}

}
実行結果:
19
Person[20,xgj]
Person[21,xgj]
Person[19,xgj]
19
結果から,Main関数はスレッドp 1,p 2,p 3の実行前後でPersonオブジェクトのage値は変わらず,p 1,p 2,p 3はPerson値を取得した後にageの値を修正したが,3つのスレッド間では互いに影響せず,操作はいずれも現在のスレッドバインドのpersonオブジェクトであることが分かる.
では、ThreadLocalの原理は具体的に何なのでしょうか.
まず、ThreadLocalのソースコードが提供するいくつかの方法を見てみましょう.
.1)set(T value)
 public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
このメソッドから、まず現在のスレッドに関連するThreadLocalMapを取得し、次に変数の値をこのThreadLocalMapオブジェクトに設定します.もちろん、取得したThreadLocalMapオブジェクトが空の場合、createMapメソッドによって作成されます.ThreadLocalMapというオブジェクトを見てみましょう.ThreadLocalMapはThreadLocalクラスの静的内部クラスで、キー値ペアの設定と取得を実現しています.map.set(this,value)、ここでthisはThreadLocalオブジェクトを指し、値はあなたが設定したオブジェクトです.getMapとcreateMapメソッドの実装を見てみましょう
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
ThreadLocal.ThreadLocalMap threadLocals = null;

createMapは次のように実装されています.
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

ThreadLocalMapコードには、以下のような弱い参照があることにも気づきました.
 static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
弱引用を使用しているのは、ThreadLocalが強引用を失ったときに、TreadLocalに対応するEntryが次回のgcで回収され、回収された空間が多重化され、メモリ漏洩をある程度回避できるためであることに注意してください.
.2)T 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();
    }
getメソッドは現在のスレッドからThreadLocalMapを取得し、ThreadLocalMapが空でない場合はThreadLocal keyに従ってThreadLocalMapからvalue値を取得し、ThreadLocalMapが存在しない場合は初期化メソッドsetInitialValue()を呼び出し、setInitialValue()メソッドを見てみましょう.
 private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
このメソッドでは、set(T value)メソッドと基本的に呼応します.この行のコードT value=initialValue()に注意してください.ここでメソッドinitialValue()を呼び出し、以下で分析します.
.3)initialValue()
ソース実装を見てみましょう
protected T initialValue() {
        return null;
    }
本方法はnullを返し、この方法がThreadLocalサブクラスによって書き換えられるべきであること、すなわちThreadLocal実装クラス実装Tの付与があることを示す.
上記のいくつかの方法の分析を通して、私達は基本的にThreadLocalの動作原理を理解して、各スレッドはすべて1つのThreadLocalMapオブジェクトを持って、その中のThreadLocalオブジェクトはkeyで、valueはスレッドの共有オブジェクトのコピーで、このようにスレッドのアクセスがないのはすべて本スレッドの変数のコピーで、それによってスレッド間のアクセスの隔離を実現しました.
.4)remove()
 public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
現在のスレッドローカル変数の値を削除し、メモリの使用を減らす目的で使用します.スレッドが終了すると、対応するスレッドのローカル変数がゴミによって自動的に回収されるので、スレッドローカル変数を呼び出す必要はありませんが、メモリ回収速度を速めることができます.