面試大廠必定離不開的——ThreadLocal,它的實現原理你知道嗎?

使用場景

假設咱們有一個數據庫鏈接管理類:面試

class ConnectionManager {
    private static Connection connect = null;
    private static String url = System.getProperty("URL");

    public static Connection openConnection() {
        if(connect == null){
            try {
                connect = DriverManager.getConnection(url);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return connect;
    }

    public static void closeConnection() {
        if(connect!=null) {
            try {
                connect.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

若是這個類被用在多線程環境內,則會存在線程安全問題,那麼能夠對這兩個方法添加synchronized關鍵字進行同步處理,不過這樣會大大下降程序的性能,也能夠將connection變成局部變量:數據庫

class ConnectionManager {
    private Connection connect = null;

    public Connection openConnection(String url) {
        if(connect == null){
            try {
                connect = DriverManager.getConnection(url);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return connect;
    }

    public void closeConnection() {
        if(connect!=null) {
            try {
                connect.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

class ConnectionManagerTest {
    private String url = System.getProperty("URL");

    public void insert() {
        ConnectionManager connectionManager = new ConnectionManager();
        Connection connection = connectionManager.openConnection(this.url);
        //使用connection進行操做
        connectionManager.closeConnection();
    }
    public void update() {
        ConnectionManager connectionManager = new ConnectionManager();
        Connection connection = connectionManager.openConnection(this.url);
        //使用connection進行操做
        connectionManager.closeConnection();
    }
}

每一個CURD方法都建立新的數據庫鏈接會形成數據庫的很大壓力,這裏能夠有兩種解決方案:數組

  1. 使用鏈接池管理鏈接,既不是每次都建立、銷燬鏈接,而是從一個鏈接池裏借出可用的鏈接,用完將其歸還。安全

  2. 能夠看到,這裏connection的創建最好是這樣的:每一個線程但願有本身獨立的鏈接來避免同步問題,在線程內部但願共用同一個鏈接來下降數據庫的壓力,那麼使用ThreadLocal來管理數據庫鏈接就是最好的選擇了。它爲每一個線程維護了一個本身的鏈接,而且能夠在線程內共享。
class ConnectionManager {
    private static String url = System.getProperty("URL");
    private static ThreadLocalconnectionHolder = ThreadLocal.withInitial(() -> {
        try {
            return DriverManager.getConnection(url);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    });

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

    public static void closeConnection() {
        Connection connect = connectionHolder.get();
        if(connect!=null) {
            try {
                connect.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

另外還能夠用到其餘須要每一個線程管理一份本身的資源副本的地方:An Introduction to ThreadLocal in Java多線程

實現原理

這裏面涉及到三種對象的映射:Thread-ThreadLocal對象-ThreadLocal中存的具體內容,既然是每一個線程都會有一個資源副本,那麼這個從ThreadLocal對象到存儲內容的映射天然就會存在Thread對象裏:ide

ThreadLocal.ThreadLocalMap threadLocals = null;

而ThreadLocal類只是提供了訪問這個Map的接口:性能

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

這個ThreadLocalMap是ThreadLocal的內部類,實現了一個相似HashMap的功能,其內部維護了一個Entry數組,下標就是經過ThreadLocal對象的threadLocalHashCode計算得來。這個Entry繼承自WeakReference,實現對key,也就是ThreadLocal的弱引用:this

static class Entry extends WeakReference<threadlocal
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal k, Object v) {
        super(k);
        value = v;
    }
}
</threadlocal

內存模型圖以下:url

面試大廠必定離不開的——ThreadLocal,它的實現原理你知道嗎?

當ThreadLocal Ref出棧後,因爲ThreadLocalMap中Entry對ThreadLocal只是弱引用,因此ThreadLocal對象會被回收,Entry的key會變成null,而後在每次get/set/remove ThreadLocalMap中的值的時候,會自動清理key爲null的value,這樣value也能被回收了。線程

注意:若是ThreadLocal Ref一直沒有出棧(例如上面的connectionHolder,一般咱們須要保證ThreadLocal爲單例且全局可訪問,因此設爲static),具備跟Thread相同的生命週期,那麼這裏的虛引用便形同虛設了,因此使用完後記得調用ThreadLocal.remove將其對應的value清除。

另外,因爲ThreadLocalMap中只對ThreadLocal是弱引用,對value是強引用,若是ThreadLocal由於沒有其餘強引用而被回收,以後也沒有調用過get/set,那麼就會產生內存泄露,

在使用線程池時,線程會被複用,那麼裏面保存的ThreadLocalMap一樣也會被複用,會形成線程之間的資源沒有被隔離,因此在線程歸還回線程池時要記得調用remove方法。

hash衝突

上面提到ThreadLocalMap是本身實現的相似HashMap的功能,當出現Hash衝突(經過兩個key對象的hash值計算獲得同一個數組下標)時,它沒有采用鏈表模式,而是採用的線性探測的方法,既當發生衝突後,就線性查找數組中空閒的位置。

當數組較大時,這個性能會不好,因此建議儘可能控制ThreadLocal的數量。

相關文章
相關標籤/搜索