深入探討java.lang.ThreadLocal類

深入探討java.lang.ThreadLocal類

一、概述

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

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

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

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

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

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

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

概括起來(lái)說(shuō),對(duì)于多線程資源共享問(wèn)題,同步機(jī)制采用了“以時(shí)間換空間”的方式,而ThreadLocal采用了“以空間換時(shí)間”的方式。前者僅提供一份變量,在不同線程排隊(duì)訪問(wèn),而后者為每一個(gè)線程都提供了一份變量,因此可以同時(shí)訪問(wèn)而不互相影響。

二、API說(shuō)明

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


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


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


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


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

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

三、代碼實(shí)例

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("從未放過(guò)值");
            t1.set("我的值");
        }
        System.out.println(t1.get());
        System.out.println(t1.get());

    }
}
運(yùn)行結(jié)果:
從未放過(guò)值
我的值
我的值

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

3.2 Hibernate的Session 工具類HibernateUtil

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

public class HibernateUtil {
    private static Log log = LogFactory.getLog(HibernateUtil.class);
    private static final SessionFactory sessionFactory;     //定義SessionFactory
 
    static {
        try {
            // 通過(guò)默認(rèn)配置文件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,用來(lái)保存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還沒(méi)有打開(kāi),則新開(kāi)一個(gè)Session
        if (s == null) {
            s = sessionFactory.openSession();
            session.set(s);         //將新開(kāi)的Session保存到線程局部變量中
        }
        return s;
    }
 
    public static void closeSession() throws HibernateException {
        //獲取線程局部變量,并強(qiáng)制轉(zhuǎn)換為Session類型
        Session s = (Session) session.get();
        session.set(null);
        if (s != null)
            s.close();
    }
}

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

3.3 驗(yàn)證線程變量的隔離性
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存儲(chǔ)每一個(gè)線程的私有數(shù)據(jù)

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

3.4 解決get() 返回null問(wèn)題
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 "我是默認(rèn)值 第一次get不再為null";
    }
}

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

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

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

3.5 再次驗(yàn)證線程變量的隔離性
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();
        }
    }
}

運(yùn)行結(jié)果:

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容