併發工具類ThreadLocal詳解

話不多說,直奔主題

源碼分析

/**
 *這個類可以爲每個線程分配某個類獨立對象(局部變量),達到線程安全通常
 *是工具類,典型的SimpleDateFormat和Random,還有就是爲每個線程內需要保存全
 *局變量可以讓不同方法直接使用,避免參數傳遞的額麻煩
 * 
 * 當某個線程消失後,分配給這個線程的對象副本會被GC
 *
 * @author  Josh Bloch and Doug Lea
 * @since   1.2
 */
public class ThreadLocal<T> {
//構造方法
 /**
     * 創建一個本地局部變量.
     */
    public ThreadLocal() {
    }

//重要方法


 /**
     *將當前線程的此線程局部變量設置爲指定的值。 大多數子類將無需重
     *寫此方法,僅依靠initialValue()方法設置局部變量,也就是初始化
     *
     * @return 對象的副本
     */
    public T get() {
    //當前線程
        Thread t = Thread.currentThread();
    /**
     *Thread類的屬性,用鍵值對存放每個線程的threadLocals
     *  ThreadLocal.ThreadLocalMap threadLocals = null;
     */
      //獲取maps
        ThreadLocalMap map = getMap(t);
        if (map != null) {
        //map的鍵就是本類對象也就是ThreadLocal對象
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                //如果值不爲空直接返回返回
                return result;
            }
        }
        //初始化值並返回
        return setInitialValue();
    }
 /**
     * 返回此線程局部變量的當前線程的“初始值”。 該方法將在第一次使用get()
     * 方法訪問變量時被調用,除非線程先前調用了set(T)方法,在這種情況下,
     *  initialValue方法將不會被調用。 通常情況下,這種方法最多每個線程調
     * 用一次,但它可能會再次調用,在remove()後或者其次是get()首次調用。
     * 這個實現簡單地返回null ; 如果希望線程局部變量具有除null之外的
     * 初始值,則ThreadLocal必須被子類化,並且該方法被覆蓋。 通常,將使用
     * 匿名內部類。
     *
     * @return 這個線程本地的初始值
     */
    protected T initialValue() {
        return null;
    }


  /**
     * 設置初始化值
     *
     * @return 初始值
     */
    private T setInitialValue() {
    //真正初始化方法,並返回值的副本
        T value = initialValue();
        Thread t = Thread.currentThread();
        //線程存放ThreadLocal對象的map
        ThreadLocalMap map = getMap(t);
        if (map != null)
        //將值設置進去,鍵是本類對象
            map.set(this, value);
        else
        //初始化map,並傳入一個鍵值對
            createMap(t, value);
            //返回值
        return value;
    }
  /**
     *創建線程局部變量。變量的初始值是通過調用Supplier的get方法來確定的。
     *
     * @param <S> the type of the thread local's value
     * 
     * @return 一個新的線程本地變量
     * @throws NullPointerException 
     * @since 1.8
     */
    public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }


  /**
     * 將當前線程的此線程局部變量的副本設置爲指定的值。 
     * 大多數子類將無需重寫此方法,僅依靠initialValue()方法設置線程本地值
     * 的值。
     *
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
         //線程存放ThreadLocal對象的map
        ThreadLocalMap map = getMap(t);
        if (map != null)
         //將值設置進去,鍵是本類對象
            map.set(this, value);
        else
         //初始化map,並傳入一個鍵值對
            createMap(t, value);
    }



    /**
     * 移除局部變量的值,之後如果想獲取這個值,將通過調用其initialValue()
     * 方法重新初始化 ,除非其值在中間由當前線程set()過。 這可能導致當前線
     * 程中的initialValue方法的多次調
     * 用。
     *
     * @since 1.5
     */
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

案例分析1

/**
 * 描述:     1000個打印日期的任務,用線程池來執行
 */
public class ThreadLocalNormalUsage03 {

    public static ExecutorService threadPool = Executors.newFixedThreadPool(10);
    static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    String date = new ThreadLocalNormalUsage03().date(finalI);
                    System.out.println(date);
                }
            });
        }
        threadPool.shutdown();
    }

    public String date(int seconds) {
        //參數的單位是毫秒,從1970.1.1 00:00:00 GMT計時
        Date date = new Date(1000 * seconds);
        return dateFormat.format(date);
    }
}

。。。。
pool-1-thread-51970-01-01 08:00:34
pool-1-thread-41970-01-01 08:00:24
pool-1-thread-61970-01-01 08:00:24
pool-1-thread-91970-01-01 08:00:22
。。。。
從結果看發生線程不安全,存在相同的時間
解決辦法1加鎖來解決線程安全問題

/**
 * 描述:     加鎖來解決線程安全問題
 */
public class ThreadLocalNormalUsage04 {

    public static ExecutorService threadPool = Executors.newFixedThreadPool(10);
    static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    String date = new ThreadLocalNormalUsage04().date(finalI);
                    System.out.println(date);
                }
            });
        }
        threadPool.shutdown();
    }

    public String date(int seconds) {
        //參數的單位是毫秒,從1970.1.1 00:00:00 GMT計時
        Date date = new Date(1000 * seconds);
        String s = null;
        synchronized (ThreadLocalNormalUsage04.class) {
            s = dateFormat.format(date);
        }
        return s;
    }
}

枷鎖雖然能保證線程安全,但是存在阻塞,效率低下

解決辦法2ThreadLocal爲每一個線程分配不同的對象保證線程安全

/**
 * 描述:     利用ThreadLocal,給每個線程分配自己的dateFormat對象,保證了線程安全,高效利用內存
 */
public class ThreadLocalNormalUsage05 {

    public static ExecutorService threadPool = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    String date = new ThreadLocalNormalUsage05().date(finalI);
                    System.out.println(date);
                }
            });
        }
        threadPool.shutdown();
    }

    public String date(int seconds) {
        //參數的單位是毫秒,從1970.1.1 00:00:00 GMT計時
        Date date = new Date(1000 * seconds);
//        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        SimpleDateFormat dateFormat = ThreadSafeFormatter.dateFormatThreadLocal2.get();
        return dateFormat.format(date);
    }
}

class ThreadSafeFormatter {

    public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };

//    public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal2 = ThreadLocal
//            .withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
    
    public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal2 =ThreadLocal.withInitial(new Supplier<SimpleDateFormat>() {

		@Override
		public SimpleDateFormat get() {
			// TODO Auto-generated method stub
			return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		}
	});
    
}

方法2爲每個線程綁定一個SimpleDateFormat對象,也就是線程池的線程,這裏爲10,而且不存在阻塞,效率高

案例分析2

/**
 * 描述:     演示ThreadLocal用法2:避免傳遞參數的麻煩
 */
public class ThreadLocalNormalUsage06 {

    public static void main(String[] args) {
        new Service1().process("");

    }
}

class Service1 {

    public void process(String name) {
        User user = new User("Hello World");
        UserContextHolder.holder.set(user);
        new Service2().process();
    }
}

class Service2 {

    public void process() {
        User user = UserContextHolder.holder.get();
        ThreadSafeFormatter.dateFormatThreadLocal.get();
        System.out.println("Service2拿到用戶名:" + user.name);
        new Service3().process();
    }
}

class Service3 {

    public void process() {
        User user = UserContextHolder.holder.get();
        System.out.println("Service3拿到用戶名:" + user.name);
        UserContextHolder.holder.remove();
    }
}

class UserContextHolder {

    public static ThreadLocal<User> holder = new ThreadLocal<>();


}

class User {

    String name;

    public User(String name) {
        this.name = name;
    }
}

執行結果:
Service2拿到用戶名:Hello World
Service3拿到用戶名:Hello World

我們發現通過ThreadLocal在 Service1創建的對象 Service2和 Service3很容易通過ThreadLocal拿到,避免參數的傳遞

原理分析

回到get方法

 /**
     *將當前線程的此線程局部變量的副本設置爲指定的值。 大多數子類將無需重
     *寫此方法,僅依靠initialValue()方法設置局部變量,也就是初始化
     *
     * @return 對象的副本
     */
 public T get() {
    //當前線程
        Thread t = Thread.currentThread();
    /**
     *Thread類的屬性,用鍵值對存放每個線程的threadLocals
     *  ThreadLocal.ThreadLocalMap threadLocals = null;
     */
      //獲取maps
        ThreadLocalMap map = getMap(t);
        if (map != null) {
        //map的鍵就是本類對象也就是ThreadLocal對象
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                //如果值不爲空直接返回返回
                return result;
            }
        }
        //初始化值並返回
        return setInitialValue();
    }

initialValue()初始化之後,每次通過get方法獲取值,都是從map裏獲取,而map是由當前線程自己管理,通常initialValue()方法只會執行一次,除非在get之前set過或者remove過,所以每次都是獲取同一個值

最後爲了避免內存泄漏在使用了Threadlocal完,就要remove。把key刪除也就是threadlocal

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