ThreadLocal的使用及实现原理

ThreadLocal直译是本地线程,但实际上它的译名是线程局部变量(ThreadLocalVariable)。ThreadLocal诞生的目的是隔离不同线程所使用的变量,官方对它的解释是:“提供了线程局部变量,是独立于变量的初始化副本”,也就是说它可以实现将某一个变量隔离在某个线程内,其它的线程无法访问和使用这个变量。

我们先来做一个测试,先不使用ThreadLocal,创建三个线程

public class ThreadLocalTest {
	public static int num = 0;

	public static int numAdd() {
		return num++;
	}

	public static void main(String[] args) {
		Thread t1 = new Thread(new MyRunnable());
		Thread t2 = new Thread(new MyRunnable());
		Thread t3 = new Thread(new MyRunnable());
		t1.start();
		t2.start();
		t3.start();
	}

	public static class MyRunnable implements Runnable {

		@Override
		public void run() {
			for (int i = 0; i < 3; i++) {
				System.out.println(Thread.currentThread().getName() + "-" + ThreadLocalTest.numAdd());
			}
		}

	}
}

执行后发现控制台输出的是

可以发现线程执行了numAdd()方法,从0-8跑了九次,num从0加到8,也就是说线程之间共享了静态变量,从而导致线程的不安全问题。

然后我们再使用ThreadLocal来进行测试

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

	
	public static int numAdd() {
		threadLocal.set(threadLocal.get()+1);
		return threadLocal.get();
	}

	public static void main(String[] args) {
		Thread t1 = new Thread(new MyRunnable());
		Thread t2 = new Thread(new MyRunnable());
		Thread t3 = new Thread(new MyRunnable());
		t1.start();
		t2.start();
		t3.start();
	}

	public static class MyRunnable implements Runnable {

		@Override
		public void run() {
			for (int i = 0; i < 3; i++) {
				System.out.println(Thread.currentThread().getName() + "-" + numAdd());
			}
		}

	}
}

这里的numAdd方法使用了ThreadLocal的get()方法,这个方法调用了initialValue()方法并设置了返回值为0,通过调用这个方法+1,达到了num++的效果,这时候再看输出的结果

可以看到,三个不同的线程间相互隔离,变量的取值互不相干,也就是说ThreadLocal使用了不相干的变量,或者说ThreadLocal为每一个线程准备了一个变量副本,那么它是如何实现的呢,我们点进ThreadLocal的源码看看

这就是ThreadLocal的构成了,主要操作是get()和set()方法

get() : 返回当前ThreadLocal的值

 public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

set() : 将当前线程对象的值存入ThreadLocalMap中

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

首先创建了当前Thread的对象,然后存入ThreadLocalMap中,对map进行判断,不为空就将this(当前Threadlocal对象)存入作为key,并获取对应的值,最后是调用了一个setInitialValue()方法去获得初始化的值。

介绍上面两个方法主要是是为了引出ThreadLocal的实现原理,即ThreadLocalMap的创建和使用,官方注释中解释道,ThreadLocalMap是一个定制的哈希映射,只适用于维护ThreadLocal的值。在ThreadLocal类之外没有导出操作。类是包私有的,以允许在类线程中声明字段。为了帮助处理非常大且长期使用的用法,哈希表条目对键使用弱引用。但是,由于不使用引用队列,只有当表开始耗尽空间时,才开始删除陈旧的条目。

点开ThreadLocalMap,可以看到一开始ThreadLocalMap定义了一个用于存储数据的Entry 类

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

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

这个Entry类继承了弱引用类,众所周知Java有四种引用类型,其中弱引用就是每次JVM进行垃圾回收时,都会回收该对象,保证了ThreadLocal每次拷贝当前线程的值的时候所占的空间能被重新使用(若有错误请不吝指出)。

由get()方法可以得知,ThreadLocalMap的键(key)是ThreadLocal类的实例对象,value为用户的值。

那么ThreadLocalMap的引用是在哪里呢,在上面的set()方法里,调用了getMap()和createMap()方法

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

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

可以看到这边调用了一个叫threadLocals的属性,点击这个属性发现跳到了Thread类中

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

所以这个属性便是ThreadLocalMap的引用了,那么ThreadLocal的实现原理也就很清晰了:

1. 定义了一个ThreadLocalMap内部类,使用的是Map的键值对方式来存取数据,key是ThreadLocal类的实例对象,value为传值

2. 创建新的ThreadLocal对象,调用set()或get()方法时,也就是调用了ThreadLocalMap来进行操作

3. 使用ThreadLocal时,线程所使用的变量是独享的(私有的变量副本),其他线程无法访问,在使用过后(线程结束),这些变量会被GC回收

使用ThreadLocal的原因:

ThreadLocal可以用来把实例变量共享成全局变量,让程序中所有的方法都可以访问到该变量。由于存到ThreadLocal的变量都是当前线程本身,其他线程无法访问,存到ThreadLocal中只是为了方便在程序中同一个线程之间传递这个变量(和解决线程安全没有关系)。

扫码关注我的微信公众号:Java架构师进阶编程  获取最新面试题,电子书

专注分享Java技术干货,包括JVM、SpringBoot、SpringCloud、数据库、架构设计、面试题、电子书等,期待你的关注!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

方木丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值