ThreadLocal的基本用法

1.基本用法

Java ThreadLocal類容許您建立只能由同一線程讀寫的變量。所以,即便兩個線程正在執行相同的代碼,而且代碼引用了相同的ThreadLocal變量,這兩個線程也不能看到彼此的ThreadLocal變量。所以,Java ThreadLocal類提供了一種使代碼線程安全的簡單方法。java

//建立
private ThreadLocal threadLocal = new ThreadLocal();
//一旦建立了ThreadLocal,就能夠使用它的set()方法設置要存儲在其中的值。
threadLocal.set("A thread local value");
//獲取值
String threadLocalValue = (String) threadLocal.get();
//移除一個值
threadLocal.remove();

2. 普通ThreadLocal

//您能夠建立具備泛型類型的ThreadLocal。使用泛型類型時,只能將泛型類型的對象設置爲ThreadLocal的值。
//此外,您沒必要對get()返回的值進行類型轉換。
private ThreadLocal<String> myThreadLocal = new ThreadLocal<String>();
//如今只能在ThreadLocal實例中存儲字符串。另外,您不須要對從ThreadLocal得到的值進行類型轉換
myThreadLocal.set("Hello ThreadLocal");
String threadLocalValue = myThreadLocal.get();

3.初始化ThreadLocal的值

能夠爲Java ThreadLocal設置一個初始值,該值將在第一次調用get()時使用.
有兩種方式指定ThreadLocal初始值:安全

3.1建立一個ThreadLocal子類,該子類覆蓋initialValue()方法:

//爲Java ThreadLocal變量指定初始值的第一種方法是建立ThreadLocal的子類,該子類覆蓋了它的initialValue()方法。
//建立ThreadLocal子類的最簡單方法是直接在建立ThreadLocal變量的地方建立一個匿名子類。下面是建匿名子類的示例,
//該子類覆蓋了initialValue()方法
private ThreadLocal myThreadLocal = new ThreadLocal<String>() {
    @Override protected String initialValue() {
        return String.valueOf(System.currentTimeMillis());
    }
};
//注意,不一樣的線程仍然會看到不一樣的初始值。每一個線程將建立本身的初始值。
//只有當從initialValue()方法返回徹底相同的對象時,全部線程纔會看到相同的對象。
//可是,首先使用ThreadLocal的所有意義在於避免不一樣的線程看到相同的實例

3.2建立具備Supplier接口實現的ThreadLocal。

//爲Java ThreadLocal變量指定初始值的第二種方法是使用其內部的靜態工廠方法(Supplier),將Supplier接口實現做爲參數傳遞。
//這個Supplier實現爲ThreadLocal提供初始值。
//下面是一個使用其withInitial()靜態工廠方法建立ThreadLocal的示例,該方法傳遞一個簡單的供應商實現做爲參數
ThreadLocal<String> threadLocal = ThreadLocal.withInitial(new Supplier<String>() {
    @Override
    public String get() {
        return String.valueOf(System.currentTimeMillis());
    }
});
//Java8 lambda表達式的寫法
ThreadLocal threadLocal = ThreadLocal.withInitial(
        () -> { return String.valueOf(System.currentTimeMillis()); } );
//還能夠更短
ThreadLocal threadLocal3 = ThreadLocal.withInitial(
        () -> String.valueOf(System.currentTimeMillis()) );

4.延遲設置ThreadLocal的值

//在某些狀況下,您不能使用設置初始值的標準方法。例如,您可能須要一些在建立ThreadLocal變量時不可用的配置信息。
//在這種狀況下,能夠延遲設置初始值。
//下面是如何在Java ThreadLocal上惰性地設置初始值的示例
public class MyDateFormatter {

    private ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = new ThreadLocal<>();

    public String format(Date date) {
        SimpleDateFormat simpleDateFormat = getThreadLocalSimpleDateFormat();
        return simpleDateFormat.format(date);
    }
    
    private SimpleDateFormat getThreadLocalSimpleDateFormat() {
        SimpleDateFormat simpleDateFormat = simpleDateFormatThreadLocal.get();
        if(simpleDateFormat == null) {
            simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            simpleDateFormatThreadLocal.set(simpleDateFormat);
        }
        return simpleDateFormat;
    }
}
//注意format()方法是如何調用getThreadLocalSimpleDateFormat()方法來獲取Java SimpleDatFormat實例的。
//若是在ThreadLocal中沒有設置SimpleDateFormat實例,那麼就會在ThreadLocal變量中建立並設置一個新的SimpleDateFormat。
//一旦線程在ThreadLocal變量中設置了本身的SimpleDateFormat,就會對該線程使用相同的SimpleDateFormat對象繼續前進。
//但只是爲了那條線。每一個線程建立本身的SimpleDateFormat實例,由於它們不能看到在ThreadLocal變量上設置的其餘實例。

//SimpleDateFormat類不是線程安全的,所以多個線程不能同時使用它。爲了解決這個問題,
//上面的MyDateFormatter類爲每一個線程建立一個SimpleDateFormat,
//所以調用format()方法的每一個線程將使用本身的SimpleDateFormat實例。

5.多線程狀況下使用 ThreadLocal

若是您計劃從傳遞給Java線程池或Java ExecutorService的任務內部使用Java ThreadLocal,請記住,您不能保證哪一個線程將執行您的任務。
可是,若是您所須要的只是確保每一個線程使用某個對象的本身的實例,那麼這不是問題。而後,您能夠將Java ThreadLocal與線程池或ExecutorService一塊兒使用。多線程

5.1 完整的ThreadLocal 示例

public class ThreadLocalExample {

    public static void main(String[] args) {
        MyRunnable sharedRunnableInstance = new MyRunnable();

        Thread thread1 = new Thread(sharedRunnableInstance);
        Thread thread2 = new Thread(sharedRunnableInstance);

        thread1.start();
        thread2.start();

        thread1.join(); //wait for thread 1 to terminate
        thread2.join(); //wait for thread 2 to terminate
    }

}
//
public class MyRunnable implements Runnable {

    private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();

    @Override
    public void run() {
        threadLocal.set( (int) (Math.random() * 100D) );

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
        }

        System.out.println(threadLocal.get());
    }
}

//這個例子建立了一個MyRunnable實例,
//它被傳遞給兩個不一樣的線程。兩個線程都執行run()方法,所以在ThreadLocal實例上設置不一樣的值。
//若是對set()調用的訪問是同步的,而且它不是ThreadLocal對象,那麼第二個線程就會覆蓋第一個線程設置的值。
//可是,由於它是ThreadLocal對象,因此兩個線程不能看到彼此的值。所以,它們設置和獲取不一樣的值。

6.可繼承(Inheritable) ThreadLocal

InheritableThreadLocal類是ThreadLocal的子類。與每一個線程在ThreadLocal中都有本身的值不一樣,
InheritableThreadLocal將對值的訪問權授予一個線程和該線程建立的全部子線程。下面是一個完整的Java InheritableThreadLocal示例:dom

public class InheritableThreadLocalBasicExample {

    public static void main(String[] args) {

        ThreadLocal<String> threadLocal = new ThreadLocal<>();
        InheritableThreadLocal<String> inheritableThreadLocal =
                new InheritableThreadLocal<>();

        Thread thread1 = new Thread(() -> {
            System.out.println("===== Thread 1 =====");
            threadLocal.set("Thread 1 - ThreadLocal");
            inheritableThreadLocal.set("Thread 1 - InheritableThreadLocal");

            System.out.println(threadLocal.get());
            System.out.println(inheritableThreadLocal.get());

            Thread childThread = new Thread( () -> {
                System.out.println("===== ChildThread =====");
                System.out.println(threadLocal.get());
                System.out.println(inheritableThreadLocal.get());
            });
            childThread.start();
        });

        thread1.start();

        Thread thread2 = new Thread(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("===== Thread2 =====");
            System.out.println(threadLocal.get());
            System.out.println(inheritableThreadLocal.get());
        });
        thread2.start();
    }
}


//這個例子建立了一個普通的Java ThreadLocal和一個Java InheritableThreadLocal。
//而後,示例建立一個線程,該線程設置ThreadLocal和InheritableThreadLocal的值——而後建立一個子線程,
//該子線程訪問ThreadLocal和InheritableThreadLocal的值。只有InheritableThreadLocal的值對子線程是可見的。
//最後,示例建立了第三個線程,該線程也嘗試訪問ThreadLocal和InheritableThreadLocal——可是它看不到第一個線程存儲的任何值
//輸出值:
===== Thread 1 =====
Thread 1 - ThreadLocal
Thread 1 - InheritableThreadLocal
===== ChildThread =====
null
Thread 1 - InheritableThreadLocal
===== Thread2 =====
null
null
相關文章
相關標籤/搜索