深入探讨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();
        }
    }
}
2438927-a08396e0a96f8d1c.png
类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();
        }
    }
}

运行结果:

2438927-a091ab64cb50b33d.png
运行结果各有各的值

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值