ThreadLocal的理解及使用

变量值的共享可以使用public static 变量的形式,所有的线程都使用同一个public static变量。如果想实现每一个线程都有自己的共享变量,此时我们应该想到用ThreadLocal。ThreadLocal是维持线程封闭性的一种规范的方法,通常用于防止对可变的单实例变量或全局变量进行共享。

我们先写个demo熟悉一下它的用法

package test0324;

/**
 * @ Author      : Hxj
 * @ Date        : Created in 2019/3/24
 * @ Description : ThreadLocal理解及分析
 * @ Modified  by:
 * @ Version: 0.0$
 */
public class ThreadLocalTest {

    public static class Tools{
        public static ThreadLocal tle = new ThreadLocal();
        public static void setToolThreadLocalValue(String value){
            tle.set(value);
        }
    }

    public static class ThreadA extends Thread{

        @Override
        public void run(){
            Tools.setToolThreadLocalValue("ThreadA");
            for (int i = 0; i < 10; i++) {
                System.out.println("在ThreadA线程中取值=" + Tools.tle.get());
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static class ThreadB extends Thread{

        @Override
        public void run(){
            Tools.setToolThreadLocalValue("ThreadB");
            for (int i = 0; i < 10; i++) {
                System.out.println("在ThreadB线程中取值=" + Tools.tle.get());
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

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

运行结果

在Main线程中取值=main
在ThreadA线程中取值=ThreadA
在ThreadB线程中取值=ThreadB
在Main线程中取值=main
在ThreadA线程中取值=ThreadA
在ThreadB线程中取值=ThreadB
在Main线程中取值=main
在ThreadA线程中取值=ThreadA
在ThreadB线程中取值=ThreadB
在Main线程中取值=main
在ThreadA线程中取值=ThreadA
在ThreadB线程中取值=ThreadB
在Main线程中取值=main
在ThreadA线程中取值=ThreadA
在ThreadB线程中取值=ThreadB
在Main线程中取值=main
在ThreadA线程中取值=ThreadA
在ThreadB线程中取值=ThreadB
在Main线程中取值=main
在ThreadA线程中取值=ThreadA
在ThreadB线程中取值=ThreadB
在Main线程中取值=main
在ThreadA线程中取值=ThreadA
在ThreadB线程中取值=ThreadB
在Main线程中取值=main
在ThreadA线程中取值=ThreadA
在ThreadB线程中取值=ThreadB
在Main线程中取值=main
在ThreadA线程中取值=ThreadA
在ThreadB线程中取值=ThreadB

我们可以看到ThreadA和ThreadB和Main线程都是用的同一个ThreadLocal对象,每次从里面取出的值都是自己线程放进去的值,并不会发生值紊乱的情况从而保证了线程封闭性。

比如在单线程应用程序中可能要维持一个全局的数据库连接,并在程序启动时初始化这个连接对象,从而避免在调用每个方法时都要传递一个Connection对象。由于JDBC的连接对象不一定是线程安全的,因此当多线程应用程序在没有协同的情况下使用全局变量时就不是线程安全的,此时我们可以通过将Connection对象放在ThreadLocal对象中,使得每个线程都会拥有属于自己的连接。

private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>(){
     //初始化连接
     @Override
     public Connection initialValue() throws SQLException {
         return DriverManager.getConnection("DB_URL");
     }
};

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

每个线程在初次调用ThreadLocal.get方法时,都会调用initialValue来获取初始值。从概念上看,你可以将ThreadLocal<T>视为包含了Map<Thread,T>对象,其中保存了特定于该线程的值,但ThreadLocal的实现并非如此。这些特定于线程的值保存在Thread对象中,当线程终止后,这些值会作为垃圾被回收。

private void exit() {
    if (group != null) {
        group.threadTerminated(this);
        group = null;
    }
    /* Aggressively null out all reference fields: see bug 4006245 */
    target = null;
    /* Speed the release of some of these resources */
    threadLocals = null;
    inheritableThreadLocals = null;
    inheritedAccessControlContext = null;
    blocker = null;
    uncaughtExceptionHandler = null;
}

假如你现在将一个单线程应用程序移植到多线程环境中,通过将共享的全局变量转换为ThreadLocal对象,可以维持线程安全性。

在我们现有的应用程序框架中,当我们使用事务的时候,可能需要将事务上下文与某个执行中的线程关联起来,通过将事务上下文保存在ThreadLocal对象中,当代码需要判断当前运行的是哪一个事务时,只需从这个ThreadLocal对象中读取事务上下文信息即可,从而避免了每次执行都要传递事务上下文信息,但是这样我们发现无形之中增加了类之间的耦合性,所以使用ThreadLocal的时候要慎重。

上面我们知道,一个线程的ThreadLocal对象是存储在线程本身之内的

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal.ThreadLocalMap其实就是一个Map结构,key为ThreadLocal,value为值,当我们执行ThreadLocal.get()方法时,其实是拿着key去该Map结构中获取值了。

如果线程不终止,ThreadLocal对象永远不会被回收,永远存在于线程的ThreadLocal.ThreadLocalMap中,所以我们需要注意如果不再使用ThreadLocal对象时调用remove()方法将其value置为null以防发生内存泄露,下次调用get()方法的时候,如果发现值为空,会自动进行初始化的。

另如果我们在使用线程池的时候用到了ThreadLocal,线程池中的核心线程永远不会被回收,当次线程ThreadLocal赋完值,如果不手动执行ThreadLocal.ThreadLocalMap threadLocals = null;ThreadLocal会一直是同一个值,这个在使用线程池的时候要注意一下。

参考文献:

《java多线程编程核心技术》 高洪岩 著

《java并发编程实战》Brian Goetz 等著  童云兰 等译

  https://blog.csdn.net/puppylpg/article/details/80433271

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值