深入探讨java.lang.ThreadLocal类

深入探讨java.lang.ThreadLocal类

一、概述

ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是一个ThreadLocalVariable(线程局部变量)。

变量值的共享可以使用 public static 变量的形式,所有的线程都使用同一个 public static 变量. 如果想实现每一个每一个线程都有自己的共享变量该如何解决呢? JDK中提供的类ThreadLocal正是为了解决这样的问题.
ThreadLocal主要解决的就是每个线程绑定自己的值,可以将 ThreadLocal类比喻成全局存放数据的盒子,盒子中可以存储每个线程的私有数据.

从线程角度看,只要线程是活动的并且 ThreadLocal实例时可访问的,那么每个线程都保持一个对其线程共享的私有变量副本的隐式引用,在线程消失之后,其线程的所有私有变量副本都会被垃圾回收(除非存在对这些变量副本的其他引用)。

通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。

ThreadLocal 是如何做到为每一个线程维护私有变量副本的呢?其实实现的思路很简单,在以前,底层实现是一个HashMap,key是当前线程,value是该实例。但是现在的设计思路改了!!现在的底层实现是Thread个HashMap,每个HashMap的key是这个ThreadLocal实例,value是那个对象的副本。
ThreadLocal在1.6版本后是在Thread类中有一个ThreadLocalMap的变量,然后用Thread.currentThread().threadLocals.get(this)来引用的各线程变量副本.

为什么这样搞呢?如果是原来的设计方案,那么在大型项目里有很多Thread和很多ThreadLocal的前提下,就会有ThreadLocal个HashMap,每个里面就有Thread个元素。在Thread很多的情况下性能会低。

还有一点,当一个线程停止时,对应的ThreadLocal副本都不存在了,可以销毁一个HashMap。但用第一种设计思路的话这些HashMap都在。

概括起来说,对于多线程资源共享问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,在不同线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而不互相影响。

二、API说明

ThreadLocal() -->创建一个线程本地变量


get() -->返回线程本地变量的当前线程副本中的值,如果第一次调用get()方法则返回的值是null


protected T initialValue() --> 返回此线程本地变量的当前线程的初始值。最多在每次访问线程来获得每个线程局部变量时调用此方法一次,即线程第一次使用 get() 方法访问变量的时候。如果线程先于 get 方法调用 set(T) 方法,则不会在线程中再调用 initialValue 方法


set(T value) -->将线程本地变量的当前线程副本中的值设置为指定值.许多应用程序不需要此方法,它们只依赖于initialValue()方法来设置线程局部变量的值.


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());

    }
}
运行结果:
从未放过值
我的值
我的值

从上面的运行结果来看,第一次调用t1对象的get()方法时返回的值是null,通过调用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创建SessionFactory
            sessionFactory = new Configuration().configure().buildSessionFactory();
        } catch (Throwable ex) {
            log.error("初始化SessionFactory失败!", ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    //创建线程局部变量session,用来保存Hibernate的Session
    public static final ThreadLocal session = new ThreadLocal();
 
    /**
     * 获取当前线程中的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 {
        //获取线程局部变量,并强制转换为Session类型
        Session s = (Session) session.get();
        session.set(null);
        if (s != null)
            s.close();
    }
}

在这个类中,由于没有重写ThreadLocal的initialValue()方法,则首次创建线程局部变量session其初始值为null,第一次调用currentSession()的时候,线程局部变量的get()方法也为null。因此,对session做了判断,如果为null,则新开一个Session,并保存到线程局部变量session中,这一步非常的关键,这也是public static final ThreadLocal session = new ThreadLocal()所创建的对象session通过get()获取的对象能强制转换为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存储每一个线程的私有数据

虽然三个线程都向t1对象中set()数据值,但每个线程还是能取出自己的数据。

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());
    }
}
运行结果:
我是默认值 第一次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();
        }
    }
}

运行结果:

运行结果各有各的值

子线程和父线程各有各自所拥有的值;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容