1. 程式人生 > >ThreadLocal-單例模式下高併發執行緒安全

ThreadLocal-單例模式下高併發執行緒安全

MirrorThread

public class MirrorThread extends Thread{
	private Mirror mirror;
	
	private String threadName;
	
	public MirrorThread(Mirror mirror, String threadName){
		this.mirror = mirror;
		this.threadName = threadName;
	}
		
	//照鏡子
	public String lookMirror() {
		return threadName+" looks like "+ mirror.getNowLookLike().get();
	}
	
	//化妝
	public void makeup(String makeupString) {
		mirror.getNowLookLike().set(makeupString);
	}
	
	@Override
    public void run() {
		int i = 1;//閾值
		while(i<5) {
			try {
				long nowFace = (long)(Math.random()*5000);
				sleep(nowFace);
				StringBuffer sb = new StringBuffer();
				sb.append("第"+i+"輪從");
				sb.append(lookMirror());
				makeup(String.valueOf(nowFace));
				sb.append("變為");
				sb.append(lookMirror());
				System.out.println(sb);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			i++;
		}
	}
}

也很好理解,就是不斷的更新自己的外貌同時從鏡子裡讀取自己的外貌。


重點是Mirror:
public class Mirror {
	private String mirrorName;
	
	//每個人要看到自己的樣子,所以這裡要用ThreadLocal
	private ThreadLocal<String> nowLookLike;
	
	public Mirror(String mirrorName){
		this.mirrorName=mirrorName;
		nowLookLike = new ThreadLocal<String>();
	}

	public String getMirrorName() {
		return mirrorName;
	}

	public ThreadLocal<String> getNowLookLike() {
		return nowLookLike;
	}
}

對每個人長的樣子用ThreadLocal型別來表示。

先看測試結果:

第1輪從張三 looks like null變為張三 looks like 3008
第2輪從張三 looks like 3008變為張三 looks like 490
第1輪從王二 looks like null變為王二 looks like 3982
第1輪從李四 looks like null變為李四 looks like 4390
第2輪從王二 looks like 3982變為王二 looks like 1415
第2輪從李四 looks like 4390變為李四 looks like 1255
第3輪從王二 looks like 1415變為王二 looks like 758
第3輪從張三 looks like 490變為張三 looks like 2746
第3輪從李四 looks like 1255變為李四 looks like 845
第4輪從李四 looks like 845變為李四 looks like 1123
第4輪從張三 looks like 2746變為張三 looks like 2126
第4輪從王二 looks like 758變為王二 looks like 4516

OK,一面鏡子所有人一起照,而且每個人都只能看的到自己的變化,這就達成了單例執行緒安全的目的。

我們來細看下它是怎麼實現的。

先來看Thread

Thread中維護了一個ThreadLocal.ThreadLocalMapthreadLocals = null; ThreadLocalMap這個MapkeyThreadLocalvalue是維護的成員變數。現在的跟蹤鏈是Thread->ThreadLocalMap-><ThreadLocal,Object>,那麼我們只要搞明白Thread怎麼跟ThreadLocal關聯的,從執行緒裡找到自己關心的成員變數的快照這條線就通了。

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

再來看ThreadLocal:它裡面核心方法兩個get()set(T)

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

方法裡通過Thread.currentThread()的方法得到當前執行緒,然後做為key儲存到當前執行緒物件的threadLocals中,也就是TreadLocalMap中。

OK,這樣整個關係鏈已經建立,真正要去訪問的成員變數在一個map中,key是執行緒號,值是屬於該執行緒的快照。

ThreadLocal裡還有map的建立createMap(t, value)、取值時物件的初始值setInitialValue()、執行緒結束時物件的釋放remove()等細節,有興趣的可以繼續跟進了解下。

ThreadLocal應用其實很多,例如Spring容器中例項預設是單例的,transactionManager也一樣,那麼事務在處理時單例的manager是如何控制每個執行緒的事務要如何處理呢,這裡面就應用了大量的ThreadLocal