Java多線程之ThreadLocal

ThreadLocal簡介

ThreadLocal是Java中的線程局部變量,用於存放線程的局部變量。java

ThreadLocal爲每一個線程的中併發訪問的數據提供一個副本,經過訪問副原本運行業務,這樣的結果是耗費了內存,可是確避免線程同步所帶來性能消耗,也減小了線程併發控制的複雜度。mysql

首先看一下ThreadLocal的API:sql

  • get():返回此線程局部變量的當前線程副本中的值。
  • protected T initialValue(): 返回此線程局部變量的當前線程的「初始值」。
  • void remove(): 移除此線程局部變量當前線程的值。
  • void set(T value): 將此線程局部變量的當前線程副本中的值設置爲指定值。

能夠看出ThreadLocal內部應該就是封裝了一個Map,本身實現ThreadLocal:數據庫

public class SimpleThreadLocal {
    private Map valueMap = Collections.synchronizedMap(new HashMap());
    public void set(Object newValue) {
        //①鍵爲線程對象,值爲本線程的變量副本
        valueMap.put(Thread.currentThread(), newValue);
    }
    public Object get() {
        Thread currentThread = Thread.currentThread();

        //②返回本線程對應的變量
        Object o = valueMap.get(currentThread); 
                
        //③若是在Map中不存在,放到Map中保存起來
        if (o == null && !valueMap.containsKey(currentThread)) {
            o = initialValue();
            valueMap.put(currentThread, o);
        }
        return o;
    }
    public void remove() {
        valueMap.remove(Thread.currentThread());
    }
    public Object initialValue() {
        return null;
    }
}

以上代碼很好理解,JDK中的實現比這複雜,能夠自行查看源碼。安全

ThreadLocal基本使用

ThreadLocal對象一般用於防止對可變的單實例變量或全局變量進行共享。服務器

當一個類中使用了static成員變量的時候,必定要多問問本身,這個static成員變量須要考慮線程安全嗎?也就是說,多個線程須要獨享本身的static成員變量嗎?若是須要考慮,不妨使用ThreadLocal。多線程

例如,在單線程應用程序中可能會維護一個全局的數據庫鏈接,並在程序啓動的時候初始化這個鏈接,從而避免在調用每一個方法的時候都要傳遞一個Connection對象。因爲JDBC的鏈接對象不必定時線程安全的,所以,當多線程應用程序在沒有協同的狀況下使用全局變量時,就是否是線程安全的。經過把JDBC的鏈接保存到ThreadLocal對象中,每一個線程都會擁有本身的鏈接。架構

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class ConnectionManager {

    private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
    @Override
    protected Connection initialValue() {
        Connection conn = null;
        try {
            conn = DriverManager.getConnection(
                    "jdbc:mysql://localhost:3306/test", "username",
                    "password");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }
    };

    public static Connection getConnection() {
        return connectionHolder.get();
    }

    public static void setConnection(Connection conn) {
        connectionHolder.set(conn);
    }
}

ThreadLocal須要注意的點

只要使用了「池」(線程池、鏈接池),在使用ThreadLocal時,尤爲須要注意,每一個線程在使用ThreadLocal的時候,必須對ThreadLocal執行一次clear操做,避免出現線程污染問題。併發

線程池中的線程是重複利用的,只要線程還在,ThreadLocal線程本地變量會一直存在系統中,在JavaEE的服務器中尤其明顯。ide

引用高廣超在Java解讀-ThreadLocal詳解與應用中所說:

根據池中的線程數量(在運行環境中大於100個線程是正常的)以及ThreadLocal變量中對象的大小,可能會發生致命的內存問題。例如對線程池中的200個線程進行配置以及將ThreadLocal變量的大小設置爲5MB,這將會致使有1GB的堆空間被這些變量所佔用。這將會致使一個GC的開銷而且可能會因爲OutOfMemoryError致使JVM崩潰。

參考資料:

Java解讀-ThreadLocal詳解與應用

《架構探險》—黃勇

相關文章
相關標籤/搜索