Java 併發程式設計:ThreadLocal 簡單介紹
阿新 • • 發佈:2021-10-20
1. ThreadLocal 是如何保證執行緒安全的?
簡單來說,造成執行緒不安全的原因是多個執行緒同時去更新共享資料,處理共享資料常用的方法就是加鎖,通過加鎖的方式來控制執行緒對共享資料的訪問,例如樂觀鎖和悲觀鎖。
ThreadLocal 保證執行緒安全的方式是為每個執行緒提供一個獨立的變數副本來解決衝突問題,每個執行緒更改的都是自己的變數副本,從根本上解決解決問題。
2. ThreadLocal 使用場景
ThreadLocal 對於每個執行緒都需要擁有自己的一份本地變數副本的情況下是最為適合的,常用於 Session 會話或資料庫連線等。
阿里編碼規約上亦有通過 ThreadLocal 保證執行緒安全性的案例,在第一章第六節併發安全處理部分,用於 SimpleDateFormat 做格式化處理部分。
3. ThreadLocal 簡單使用
public class ThreadLocalTest { private static ThreadLocal<Integer> threadLocal = new ThreadLocal(); public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(new RunnableImpl()).start(); } } static class RunnableImpl implements Runnable { @Override public void run() { try { // 1、獲取隨機數 int random = (int) Math.random() * 1000; // 2、設定初始值 threadLocal.set(random); // 3、等待其他執行緒進行更新 Thread.sleep(3000); // 4、獲取初始值 System.out.println("存取內容是否一致:" + (random == threadLocal.get())); } catch (Exception e) { e.printStackTrace(); } finally { // 不需要用到資料時進行手動刪除,避免引起記憶體洩漏 threadLocal.remove(); } } } }
執行結果:
4. ThreadLocal 解析
// 常用於建立 ThreadLocal 物件同時設定初始值 public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) { return new SuppliedThreadLocal<>(supplier); } // 設定初始值為0,預設為 null private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
// set 方法用於設定初始值,這裡是根據執行緒存放資料 public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
public T get() { // 獲取當前執行緒並拿到當前執行緒的 ThreadLocalMap 引用 Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); // 若引用不為 null 則代表已例項化,可以獲取內部屬性 if (map != null) { // 獲取內部類例項,Entry為ThreadLocalMap的內部類 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { // 獲取Entry物件的value屬性,value為set方法設定的值 @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } // 進行初始化 return setInitialValue(); }
/** * 當不需要物件副本時請執行remove操作,避免記憶體溢位 * static class Entry extends WeakReference<ThreadLocal<?>> * ThreadLocalMap.Entry 例項 key 為弱引用,垃圾回收時無論記憶體是否充足都會被回收 * 此時 value 作為強引用未能進行回收但已無法通過 ThreadLocal 獲取該資料 */ public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }