ThreadLocal是怎么实现线程私有变量的

ThreadLocal的功能

在JMM中,所有的对象都存储在堆中,每个线程都能去访问堆中的对象,如果要暂时占有对象,可以使用Synchronized关键字进行加锁操作,但是这种操作只是暂时的,线程释放了锁以后这个对象又会变成共享状态。如果遇到需要生成线程私有变量的场景时,Java提供了一个叫ThreadLocal的类,用来构造线程独享的对象

public class Test {
    private static final ThreadLocal<Integer> localInstance = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    public static void main(final String[] args) {
        for (int i = 0; i < 3; i++) {
            Thread t = new Thread(new Runnable() {
                public void run() {
                    for (int i = 0; i < 2; i++) {
                        Integer n = localInstance.get();
                        localInstance.set(++n);
                        System.out.println(Thread.currentThread().getName() + " " + n);
                    }
                }
            });
            t.start();
        }
    }
}
/*
------输出------
Thread-0 1
Thread-0 2
Thread-1 1
Thread-1 2
Thread-2 1
Thread-2 2
*/

在这个程序运行过程中,尽管有3个线程在对类变量localInstance进行操作,但是每个线程之间的操作是独立的,并没有累加在一起,所以通过ThreadLocal构造的变量是可以保证线程私有的

ThreadLocal的内部实现机制

先来看ThreadLocal最基本的set和get方法

public void set(T value) {
    Thread t = Thread.currentThread();
    // 获取当前线程的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

public T get() {
    Thread t = Thread.currentThread();
    // 获取当前线程的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 然后根据ThreadLocal对象自己找到map中的Entry
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

ThreadLocalMap getMap(Thread t) {
	return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
	t.threadLocals = new ThreadLocalMap(this, firstValue);
}

不管是set还是get方法都是对一个ThreadLocalMap的对象进行操作,get时以自身为key去Map中取出变量,set时以自身为key,变量为value存进Map中。这里面的ThreadLocalMap是ThreadLocal的一个内部类,为整个机制的核心,因为每个线程的ThreadLocal变量和其对应的私有变量是以键值对的形式存储在ThreadLocalMap中,所以ThreadLocal相当于在ThreadLocalMap的基础上再封装了一层操作。而获取这个map时要传入当前线程对象是因为每个线程对象都持有一个ThreadLocalMap(threadLocals变量),存储着线程自己的私有变量

static class ThreadLocalMap {
	static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
}

看到这,线程私有变量实现机制便已经有了答案,前面测试程序中每个线程调用localInstance的get方法时,虽然操作的是同一个类变量,但get方法内部都是去线程自己的ThreadLocalMap中取出来的变量,调用set方法时也是在方法内部把变量存进线程自己的ThreadLocalMap中,这样的话每个线程真正操作的变量自然都是互相独立的

model

内存泄漏问题

ThreadLocaMap的Entry是继承了WeakReference的,把Map的key声明为了虚引用,虚引用的作用就是当没有强引用关联该对象时如果遇到GC就会被回收,举个简单的例子

String s = new String("string");
WeakReference<String> n = new WeakReference<String>(s);
System.out.println(n.get());
s = null;
System.gc();
System.out.println(n.get());
/*---输出---
string
null
*/

所以当Entry的key没有被强引用关联时就会被垃圾回收,因为ThreadLocalMap生命周期是与线程一致的,如果不再需要使用ThreadLocal对象并且ThreadLocalMap对ThreadLocal对象时强引用的情况下,ThreadLocal对象是不会被回收的,造成了内存泄漏的问题,所以Entry的key被声明为了虚引用,当不再被外部强引用时就会回收

但是这样又产生了新的问题,就是Entry中key被回收了以后,value还是继续存在吗,实践是检验真理的唯一标准,实现一个测试的环境

public class ThreadLocalTest {
    private static ThreadLocal<String> localInstance = new ThreadLocal<String>();

    public static void main(String[] args) {
        String s = new String("string");
        WeakReference<ThreadLocal> r = new WeakReference<ThreadLocal>(localInstance);
        localInstance.set(s);
        while (true) {
            Thread t = Thread.currentThread();
            s = null;
            localInstance = null;
            System.gc();
        }
    }
}

Debug运行,第一轮循环ThreadLocalMap存储着"string"字符串对象,WeakReference对象指向ThreadLocal对象

Test2

第二轮循环,WeakReference对象指向空,所以经过上一次GC后ThreadLocal对象已经被回收了,但是ThreadLocalMap还是存储着"string"字符串对象

Test1

针对这种情况,其实ThreadLocalMap有实现解决的机制,cleanSomeSlots()方法、expungeStaleEntry()方法和replaceStaleEntry()方法都是为了解决ThreadLocalMap潜在的内存泄漏问题的

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值