Java中的ThreadLocal 

 

前言

ThreadLocal是多线程中一个非常重要的对象,在面试中会被经常问到。所以明白其使用场景,使用方法以及原理和缺点是至关重要的。ThreadLocal实现了线程级别的数据隔离

 

一、什么是ThreadLocal 

答:每一个Thread对象有一个ThreadLocalMap对象(你可以理解成一个自定义的HashMap对象),ThreadLocalMap的key是ThreadLocal对象,value是一个Integer,String或者Boolean等等的对象。emmmmmm,大家不是说ThreadLocal对象是一个线程局部变量码?怎么到你这就成了一个key了呢?其实ThreadLocal对象并不存储值,只不过ThreadLocal对象的方法使得它看上去像一个存储值的线程局部变量了。然后每一个线程有一个属于自己的变量。算了,你还是讲一个例子让我们看看这对象到底是干啥子的吧!

 

二、使用场景与使用方法

最典型的是管理数据库的Connection:假设所有的线程通过DBUtil类来获得一个连接到数据库的连接(Connection),那么问题来了:每一个线程拥有一个Connection,那么我们怎么管理这些Connection呢?

  1. 在DBUtil定义一个Connection池,然后每来一个线程就给他从Connection池中拿一个Connection分配给这个线程
  2. DBUtil为每一个线程创造一个Connection,每一个线程将自己的Connection存储在自己的ThreadLocalMap中

第一种方式对于Connection的管理会很复杂,因为你需要存储Coonection和线程的对应关系,同时在线程数过大的时候池子可能装不下从而需要复杂的扩容。

第二种方式明显简单很多,当DBUtile想管理某一个线程的Connection的时候只需要通过一个共享的ThreadLocal对象就可以得到这个线程的Connection。

public class ThreadLocalTest {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new ConnectDB(),"1"); 
        Thread t2 = new Thread(new ConnectDB(),"2"); 
        t1.start();     // 线程1连接数据库
        t2.start();     // 线程2连接数据库

        t1.join();      // 等待线程1结束
        t2.join();      // 等待线程2结束
    }

}

class  ConnectDB implements Runnable{

    @Override
    public void run() {
        System.out.println(DBUtil.getConnection()); // 连接成功时返回Connection信息
    }
}

class DBUtil{
    /**
     * 线程通过此类来连接到数据库
     * 每一个线程有一个自己的连接
     * */

    private static ThreadLocal<String> connect = new ThreadLocal<>(); // key
    private static String DBName = "mysql";

    public static String getConnection(){
        if (connect.get() != null){
            return connect.get();
        }else {
            String c = Thread.currentThread() + "........DB" + " for " + DBName; // 这里用一个字符串表示一个连接
            connect.set(c); // 给当前运行线程分配一个连接
            return c;
        }
    }
}

 

三、ThreadLocal实现原理

打蛇打七寸!我们重点看一下set方法和get方法的实现原理。

  • threadLocal.set(“values”)方法:将threadLocal对象做为key,“values”作为value存储到当前线程对象的ThreadLocalMap
  • threadLocal.get()方法:返回当前线程对象ThreadLocalMap中key为threadLocal对象的value
public class ThreadLocal<T> {
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocal.ThreadLocalMap map = this.getMap(t);
        if (map != null) {
            map.set(this, value); // set线程t的ThreadLocalMap
        } else {
            this.createMap(t, value);
        }
    }

    ThreadLocal.ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;  // 返回线程t的ThreadLocalMap
    }

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue); // 初始化线程t的ThreadLocalMap
    }

    static class ThreadLocalMap {
        ....
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {  // ThreadLocalMap你看成一个自定义的HashMap就可以
            this.table = new ThreadLocal.ThreadLocalMap.Entry[16];
            int i = firstKey.threadLocalHashCode & 15;
            this.table[i] = new ThreadLocal.ThreadLocalMap.Entry(firstKey, firstValue);
            this.size = 1;
            this.setThreshold(16);
        }
    }
    
    private void set(ThreadLocal<?> key, Object value) {
        ....
    }
}

可以看出来ThreadLocal的set方法并不是在操作ThreadLocal对象,而是一直在操作线程t对象的ThreadLocalMap。

public class ThreadLocal<T> {
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocal.ThreadLocalMap map = this.getMap(t);  // 得到线程t的ThreadLocalMap
        if (map != null) {
            ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);    // 根据key来得到Entry
            if (e != null) {
                T result = e.value;  
                return result;  // 返回value
            }
        }

        return this.setInitialValue(); // 返回null
    }
}

get方法就比较简单了。

四、ThreadLocal,ThreadLocalMap,Thread关系 && 内存泄漏

我放一张别人的图:

首先我们只看堆:Thread对象中有一个ThreadLocalMap对象,ThreadLocalMap对象中有很多的个Entry,一个Entry由一个ThreadLocal对象做为key,一个Object做为value。

再看一下内存泄漏问题:当一个ThreadLocal对象不再被使用(即不再存在此对象的引用(reference))但是ThreadLocalMap中还有Entry用此对象做为Key(当然,这个Entry已经上永远都不可以使用到了的,因为程序中不存在此对象的引用了)那么此对象就不能被GC所回收从而导致内存泄漏。

解决方法上:threadLocal对象调用自身的remove方法

Conclusion

2020-8-14

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值