深入探討java.lang.ThreadLocal類

深入探討java.lang.ThreadLocal類

一、概述

ThreadLocal是什么呢?其實ThreadLocal并非是一個線程的本地實現(xiàn)版本,它并不是一個Thread,而是一個ThreadLocalVariable(線程局部變量)。

變量值的共享可以使用 public static 變量的形式,所有的線程都使用同一個 public static 變量. 如果想實現(xiàn)每一個每一個線程都有自己的共享變量該如何解決呢? JDK中提供的類ThreadLocal正是為了解決這樣的問題.
ThreadLocal主要解決的就是每個線程綁定自己的值,可以將 ThreadLocal類比喻成全局存放數(shù)據(jù)的盒子,盒子中可以存儲每個線程的私有數(shù)據(jù).

從線程角度看,只要線程是活動的并且 ThreadLocal實例時可訪問的,那么每個線程都保持一個對其線程共享的私有變量副本的隱式引用,在線程消失之后,其線程的所有私有變量副本都會被垃圾回收(除非存在對這些變量副本的其他引用)。

通過ThreadLocal存取的數(shù)據(jù),總是與當(dāng)前線程相關(guān),也就是說,JVM為每個運行的線程,綁定了私有的本地實例存取空間,從而為多線程環(huán)境常出現(xiàn)的并發(fā)訪問問題提供了一種隔離機制。

ThreadLocal 是如何做到為每一個線程維護私有變量副本的呢?其實實現(xiàn)的思路很簡單,在以前,底層實現(xiàn)是一個HashMap,key是當(dāng)前線程,value是該實例。但是現(xiàn)在的設(shè)計思路改了?。‖F(xiàn)在的底層實現(xiàn)是Thread個HashMap,每個HashMap的key是這個ThreadLocal實例,value是那個對象的副本。
ThreadLocal在1.6版本后是在Thread類中有一個ThreadLocalMap的變量,然后用Thread.currentThread().threadLocals.get(this)來引用的各線程變量副本.

為什么這樣搞呢?如果是原來的設(shè)計方案,那么在大型項目里有很多Thread和很多ThreadLocal的前提下,就會有ThreadLocal個HashMap,每個里面就有Thread個元素。在Thread很多的情況下性能會低。

還有一點,當(dāng)一個線程停止時,對應(yīng)的ThreadLocal副本都不存在了,可以銷毀一個HashMap。但用第一種設(shè)計思路的話這些HashMap都在。

概括起來說,對于多線程資源共享問題,同步機制采用了“以時間換空間”的方式,而ThreadLocal采用了“以空間換時間”的方式。前者僅提供一份變量,在不同線程排隊訪問,而后者為每一個線程都提供了一份變量,因此可以同時訪問而不互相影響。

二、API說明

ThreadLocal() -->創(chuàng)建一個線程本地變量


get() -->返回線程本地變量的當(dāng)前線程副本中的值,如果第一次調(diào)用get()方法則返回的值是null


protected T initialValue() --> 返回此線程本地變量的當(dāng)前線程的初始值。最多在每次訪問線程來獲得每個線程局部變量時調(diào)用此方法一次,即線程第一次使用 get() 方法訪問變量的時候。如果線程先于 get 方法調(diào)用 set(T) 方法,則不會在線程中再調(diào)用 initialValue 方法


set(T value) -->將線程本地變量的當(dāng)前線程副本中的值設(shè)置為指定值.許多應(yīng)用程序不需要此方法,它們只依賴于initialValue()方法來設(shè)置線程局部變量的值.


void remove() --> 移除此線程局部變量的值,這可能有助于減少線程局部變量的存儲需求。如果再次訪問此線程局部變量,那么在默認情況下它將擁有其 initialValue。

在程序中一般都重寫initialValue方法,以給定一個特定的初始值。

三、代碼實例

3.1 方法get()與null
public class ThreadLocalTest {
    public static ThreadLocal t1 = new ThreadLocal();

    public static void main(String[] args) {
        if(t1.get() == null) {
            System.out.println("從未放過值");
            t1.set("我的值");
        }
        System.out.println(t1.get());
        System.out.println(t1.get());

    }
}
運行結(jié)果:
從未放過值
我的值
我的值

從上面的運行結(jié)果來看,第一次調(diào)用t1對象的get()方法時返回的值是null,通過調(diào)用set()方法賦值后順利取出值并打印到控制臺上.說明不同線程中的值是可以放入ThreadLocal類中進行保存的;

3.2 Hibernate的Session 工具類HibernateUtil

這個類是Hibernate官方文檔中HibernateUtil類,用于Session管理;

public class HibernateUtil {
    private static Log log = LogFactory.getLog(HibernateUtil.class);
    private static final SessionFactory sessionFactory;     //定義SessionFactory
 
    static {
        try {
            // 通過默認配置文件hibernate.cfg.xml創(chuàng)建SessionFactory
            sessionFactory = new Configuration().configure().buildSessionFactory();
        } catch (Throwable ex) {
            log.error("初始化SessionFactory失?。?, ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    //創(chuàng)建線程局部變量session,用來保存Hibernate的Session
    public static final ThreadLocal session = new ThreadLocal();
 
    /**
     * 獲取當(dāng)前線程中的Session
     * @return Session
     * @throws HibernateException
     */
    public static Session currentSession() throws HibernateException {
        Session s = (Session) session.get();
        // 如果Session還沒有打開,則新開一個Session
        if (s == null) {
            s = sessionFactory.openSession();
            session.set(s);         //將新開的Session保存到線程局部變量中
        }
        return s;
    }
 
    public static void closeSession() throws HibernateException {
        //獲取線程局部變量,并強制轉(zhuǎn)換為Session類型
        Session s = (Session) session.get();
        session.set(null);
        if (s != null)
            s.close();
    }
}

在這個類中,由于沒有重寫ThreadLocal的initialValue()方法,則首次創(chuàng)建線程局部變量session其初始值為null,第一次調(diào)用currentSession()的時候,線程局部變量的get()方法也為null。因此,對session做了判斷,如果為null,則新開一個Session,并保存到線程局部變量session中,這一步非常的關(guān)鍵,這也是public static final ThreadLocal session = new ThreadLocal()所創(chuàng)建的對象session通過get()獲取的對象能強制轉(zhuǎn)換為Hibernate Session對象的原因。

3.3 驗證線程變量的隔離性
public class Tools {
    public static ThreadLocal t1 = new ThreadLocal();
}
class ThreadA extends Thread {
    @Override
    public  void run() {
        try{
            for (int i = 0; i < 20; i++) {
                Tools.t1.set("ThreadA" + (i+1));
                Thread.sleep(200);
                System.out.println("ThreadA get Value = " +Tools.t1.get());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

class ThreadB extends Thread {
    @Override
    public void run() {
        try{
            for (int i = 0; i < 20; i++) {
                Tools.t1.set("ThreadB" + (i+1));
                Thread.sleep(200);
                System.out.println("ThreadB get Value = " + Tools.t1.get());
            }
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class Run {
    public static void main(String[] args) {
        try {
            ThreadA a = new ThreadA();
            ThreadB b= new ThreadB();
            a.start();
            b.start();
            for (int i = 0; i < 20; i++) {
                Tools.t1.set("Main" + (i+1));
                Thread.sleep(200);
                System.out.println("Main get Value = " + Tools.t1.get());
            }
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
類ThreadLocal存儲每一個線程的私有數(shù)據(jù)

雖然三個線程都向t1對象中set()數(shù)據(jù)值,但每個線程還是能取出自己的數(shù)據(jù)。

3.4 解決get() 返回null問題
public class ThreadLocalExt extends ThreadLocal{
    /**
     * Returns the current thread's "initial value" for this
     * thread-local variable.  This method will be invoked the first
     * time a thread accesses the variable with the {@link #get}
     * method, unless the thread previously invoked the {@link #set}
     * method, in which case the {@code initialValue} method will not
     * be invoked for the thread.  Normally, this method is invoked at
     * most once per thread, but it may be invoked again in case of
     * subsequent invocations of {@link #remove} followed by {@link #get}.
     * <p>
     * <p>This implementation simply returns {@code null}; if the
     * programmer desires thread-local variables to have an initial
     * value other than {@code null}, {@code ThreadLocal} must be
     * subclassed, and this method overridden.  Typically, an
     * anonymous inner class will be used.
     *
     * @return the initial value for this thread-local
     */
    @Override
    protected Object initialValue() {
        return "我是默認值 第一次get不再為null";
    }
}

class run{
    public static ThreadLocalExt t1 = new ThreadLocalExt();

    public static void main(String[] args) {
        if(t1.get() == null) {
            System.out.println("從未放過值");
            t1.set("我的值");
        }
        System.out.println(t1.get());
        System.out.println(t1.get());
    }
}
運行結(jié)果:
我是默認值 第一次get不再為null
我是默認值 第一次get不再為null

此案例僅僅證明main線程有自己的值,那其他線程是否會有自己的初始值呢?

3.5 再次驗證線程變量的隔離性
public class Tools {
    public static ThreadLocalExt t1 = new ThreadLocalExt();
}
class ThreadLocalExt extends ThreadLocal {
    /**
     * Returns the current thread's "initial value" for this
     * thread-local variable.  This method will be invoked the first
     * time a thread accesses the variable with the {@link #get}
     * method, unless the thread previously invoked the {@link #set}
     * method, in which case the {@code initialValue} method will not
     * be invoked for the thread.  Normally, this method is invoked at
     * most once per thread, but it may be invoked again in case of
     * subsequent invocations of {@link #remove} followed by {@link #get}.
     * <p>
     * <p>This implementation simply returns {@code null}; if the
     * programmer desires thread-local variables to have an initial
     * value other than {@code null}, {@code ThreadLocal} must be
     * subclassed, and this method overridden.  Typically, an
     * anonymous inner class will be used.
     *
     * @return the initial value for this thread-local
     */
    @Override
    protected Object initialValue() {
        return new Date().getTime();
    }
}
class ThreadA extends Thread {
    @Override
    public  void run() {
        try{
            for (int i = 0; i < 10; i++) {
                System.out.println("在ThreadA 線程中取值 = " + Tools.t1.get());
                Thread.sleep(100);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

class Run {
    public static void main(String[] args) {
        try {
            for (int i = 0; i < 10; i++) {
                System.out.println("在Main線程中取值 = " + Tools.t1.get());
                Thread.sleep(100);
            }
            Thread.sleep(5000);
            ThreadA a = new ThreadA();
            a.start();
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

運行結(jié)果:

運行結(jié)果各有各的值

子線程和父線程各有各自所擁有的值;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容