Spring是如何處理Bean多線程下的併發問題的? ----- ThreadLocal

ThreadLocal天生爲解決相同變量的訪問衝突問題, 所以這個對於spring的默認單例bean的多線程訪問是一個完美的解決方案。spring也確實是用了ThreadLocal來處理多線程下相同變量併發的線程安全問題。

1. ThreadLocal的簡介

那麼看看jdk是怎麼說的:此類提供線程局部變量,這些變量與普通變量不同,每個線程都有自己的變量,通過ThreadLocal的get或者set方法訪問,有獨立初始化的變量副本。ThreadLocal實例通常是希望將狀態與線程關聯的類中的私有靜態字段。

2.ThreadLocal的實現原理

ThreadLocal類中提供了幾個方法:
1.public T get() { }
2.public void set(T value) { }
3.public void remove() { }
4.protected T initialValue(){ }

public T get()

    public T get() {
        //獲取當前線程
        Thread t = Thread.currentThread();
        //獲取當前線程的threadLocals變量
        ThreadLocalMap map = getMap(t);
        //如果threadLocals變量不爲null,就可以在map中查找到本地變量的值
        if (map != null) {
            //根據ThreadLocal的弱引用的key 查詢ThreadLocalMap的Value值
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //如果threadLocals爲null,則初始化當前線程的threadLocals變量
        return setInitialValue();
    }

    private T setInitialValue() {
        //此處是返回一個null
        T value = initialValue();
        //獲取當前線程
        Thread t = Thread.currentThread();
        //獲取當前線程的threadLocals變量
        ThreadLocalMap map = getMap(t);
        //如果threadLocals不爲null,就直接添加本地變量,key爲當前線程,值爲添加的本地變量值
        if (map != null)
            map.set(this, value);
            //如果threadLocals爲null,說明首次添加,需要首先創建出對應的map
        else
            createMap(t, value);
        return value;
    }
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);
    }

public void set(T value)

//話不多說,這個容易理解
   public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocal.ThreadLocalMap map = getMap(t);
        if (map != null)
        //這個是把ThreadLocal當做key,添加的屬性值爲value
            map.set(this, value);
        else
            createMap(t, value);
    }

public void remove()

使用完ThreadLocal要積極調用此方法,防止內存泄漏

  public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
         //當前線程根據ThrealLocal爲key值刪除
             m.remove(this);
     }

protected T initialValue()

對ThreadLocal初始化 使用時要自己重寫此方法設置默認值

protected T initialValue() {
        return null;
    }

ThreadLocal的使用

1、我們先來看看jdk給的例子:

public class ThreadId {
    private static final AtomicInteger nextId = new AtomicInteger(0);
    private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return nextId.getAndIncrement();
        }
    };

    public static int get() {
        return threadId.get();
    }
}
class TestID{
    public static void main(String[] args) {
        int i = ThreadId.get();
        System.out.println(i);//0
    }
}

執行此案例可以看到運行結果:是0 ,因爲上面重寫了initialValue() ,如果沒有重寫此方法,則返回結果爲null。

2、驗證線程的隔離性:

public class ThreadLocalDemo2 {
    public static ThreadLocal t1 = new ThreadLocal();
}

class ThreadA extends Thread {
    @Override
    public void run() {
        try {
            for (int i = 0; i < 100; i++) {
                ThreadLocalDemo2.t1.set("TreadA " + (i + 1));
                System.out.println("Thread get Value = " + ThreadLocalDemo2.t1.get());
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class ThreadB extends Thread {
    @Override
    public void run() {
        try {
            for (int i = 0; i < 100; i++) {
                ThreadLocalDemo2.t1.set("TreadB " + (i + 1));
                System.out.println("Thread get Value = " + ThreadLocalDemo2.t1.get());
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class Test {
    public static void main(String[] args) {
        try {
            ThreadA a = new ThreadA();
            ThreadB b = new ThreadB();
            a.start();
            b.start();
            for (int i = 0; i < 100; i++) {
                ThreadLocalDemo2.t1.set("Main " + (i + 1));
                System.out.println("Main get Value " + ThreadLocalDemo2.t1.get());
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

執行此案例可以看到運行結果:ThreadA線程、ThreadB線程、和Main線程都各自獲取自己線程存儲的值。 但是調用的卻是同一個ThreadLocal對象,那麼得出結論,ThreadLocal可以實現線程間的隔離。

3、ThreadLocal的不可繼承性:

由上面的例子可以看出,main線程和子線程間的數據是隔離的,並不能實現繼承,那麼ThreadLocal的子類InheritableThreadLocal提供了這種問題的解決方案。
下面我們通過一個案例瞭解一下:

public class ThreadLocalDemo6 {
    public static InheritableThreadLocal<Long> inheritableThreadLocal = new InheritableThreadLocal() {
        @Override
        protected Long initialValue() {
            return new Date().getTime();
        }
    };
}
class ThreadA6 extends Thread {
    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                System.out.println("在ThreadA 線程中取值 : " + ThreadLocalDemo6.inheritableThreadLocal.get());
                Thread.sleep(100);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class Test6 {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            System.out.println("在線程main 中取值爲: " + ThreadLocalDemo6.inheritableThreadLocal.get());
        }
        Thread.sleep(5000);
        ThreadA6 threadA6 = new ThreadA6();
        threadA6.start();
    }
}

執行此案例可以看到運行結果:main線程設置的屬性值,在子線程中可以獲取到,那麼可以得出InheritableThreadLocal 具有繼承的特性。

從ThreadLocalMap看ThreadLocal使用不當的內存泄漏問題

分析ThreadLocalMap內部實現

ThreadLocalMap的內部是一個Entry數組,下面我們來看一下entry的源碼

    /**
     * Entry是繼承自WeakReference的一個類,該類中實際存放的key是
     * 指向ThreadLocal的弱引用和與之對應的value值(該value值
     * 就是通過ThreadLocal的set方法傳遞過來的值)
     */
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** value就是和ThreadLocal綁定的 */
        Object value;

        //k:ThreadLocal的引用,被傳遞給WeakReference的構造方法
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

由上可知,ThreadLocal是弱引用,那麼這個引用抗不過一次GC,key可以被回收,那麼value 確不能被回收,這就可能造成內存泄漏(因爲這個時候ThreadLocalMap會存在key爲null但是value不爲null的entry項)。

規範使用ThreadLocal

在使用ThreadLocal(set()或者get()) 後,如果不在使用則要及時使用remove()方法清理資源,避免內存泄漏的問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章