1.基本用法
Java ThreadLocal類允許您創建只能由同一線程讀寫的變量。因此,即使兩個線程正在執行相同的代碼,並且代碼引用了相同的ThreadLocal變量,這兩個線程也不能看到彼此的ThreadLocal變量。因此,Java ThreadLocal類提供了一種使代碼線程安全的簡單方法。
//創建
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示例:
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