java threadlocal原理_Java ThreadLocal 原理简介

使用场景

首先看以下的程序:

public class Main {

public static class MyRunnable implements Runnable {

int val = 0;

@Override

public void run() {

val = (int) (Math.random()*100);

try {

Thread.sleep(2000);

} catch (InterruptedException e) {

}

System.out.println(val);

}

}

public static void main(String[] args) {

MyRunnable sharedRunnableInstance = new MyRunnable();

Thread thread1 = new Thread(sharedRunnableInstance);

Thread thread2 = new Thread(sharedRunnableInstance);

thread1.start();

thread2.start();

}

}

/*

可能的输出:

65

65

*/

可知,变量var是被thread1和thread2两个线程共享的,当其中一个线程修改了var以后,另一个线程再修改var,就会造成var值被覆盖掉。

这是一个典型的多个线程跑同一份代码。但是,如果我们不想这些线程共享某些变量,或者说var在每个线程内都有一个独立备份,这时候该怎么办呢?TreadLocal就是用来解决这个问题的。

例子1:

public class Main {

public static class MyRunnable implements Runnable {

private ThreadLocal threadLocal = new ThreadLocal();

@Override

public void run() {

threadLocal.set((int) (Math.random() * 100D));

try {

Thread.sleep(2000);

} catch (InterruptedException e) {

}

System.out.println(threadLocal.get());

}

}

public static void main(String[] args) {

MyRunnable sharedRunnableInstance = new MyRunnable();

Thread thread1 = new Thread(sharedRunnableInstance);

Thread thread2 = new Thread(sharedRunnableInstance);

thread1.start();

thread2.start();

}

}

/*

可能的输出:

68

34

*/

如果我需要两个不共享的变量呢?很简单,再添加一个ThreadLocal对象用来存储数据就行了。也就是说一个ThreadLocal只代表一个变量的独立备份,多个变量需要独立备份,就要有多个ThreadLocal对象来存放独立备份。

例子2:

public class Main {

public static class MyRunnable implements Runnable {

private ThreadLocal threadLocal = new ThreadLocal();

private ThreadLocal threadLocal2 = new ThreadLocal();

@Override

public void run() {

threadLocal.set((int) (Math.random() * 100D));

threadLocal2.set(Math.random() * 100D);

try {

Thread.sleep(2000);

} catch (InterruptedException e) {

}

System.out.println(threadLocal.get());

System.out.println(threadLocal2.get());

}

}

public static void main(String[] args) {

MyRunnable sharedRunnableInstance = new MyRunnable();

Thread thread1 = new Thread(sharedRunnableInstance);

Thread thread2 = new Thread(sharedRunnableInstance);

thread1.start();

thread2.start();

}

}

/*

可能的输出:

28

34

56.049492341729

11.603018021238775

*/

实现方案

从上面的ThreadLocal的使用,可以知道ThreadLocal对象也是多个线程共享的,为什么不同的线程往里面放数据,还可以从里面获取每个线程自己存放的数据呢?

方案一:ThreadLocal维护线程与实例之间的映射

既然每个访问 ThreadLocal 变量的线程都有自己的一个“本地”实例副本。一个可能的方案就是 ThreadLocal 维护一个 Map,键是 Thread,代码大概如下:

public class ThreadLocal {

private Map mMap = new HashMap<>();

public void set(Object o) {

mMap.put(Thread.currentThread(), o);

}

public Object get() {

return mMap.get(Thread.currentThread());

}

public void remove() {

mMap.remove(Thread.currentThread());

}

}

//以下为泛型形式

public class ThreadLocal {

private Map mMap = new HashMap<>();

public void set(T o) {

mMap.put(Thread.currentThread(), o);

}

public T get() {

return mMap.get(Thread.currentThread());

}

public void remove() {

mMap.remove(Thread.currentThread());

}

}

da57955ac6a59cc02e132d3dbb6ba221.png

该方案可满足上文提到的每个线程内一个独立备份的要求。每个新线程访问该 ThreadLocal 时,需要向 Map 中添加一个映射,而每个线程结束时,应该清除该映射。这里就有两个问题:

增加线程与减少线程均需要写 Map,故需保证该 Map线程安全。虽然从ConcurrentHashMap的演进看Java多线程核心技术一文介绍了几种实现线程安全 Map的方式,但它或多或少都需要锁来保证线程的安全性

线程结束时,需要保证它所访问的所有 ThreadLocal 中对应的映射均删除,否则可能会引起内存泄漏

其中锁的问题,是 JDK 未采用该方案的一个原因。

方案二:Thread维护ThreadLocal与实例的映射

上述方案中,出现锁的问题,原因在于多线程访问同一个 Map。如果该 Map 由 Thread 维护,从而使得每个 Thread 只访问自己的 Map,那就不存在多线程写的问题,也就不需要锁。代码如下:

public class Main {

public static class Mythread extends Thread {

private HashMap container = null;

public Mythread(Runnable r) {

super(r);

}

public void putData(Object key, Object data) {

if (container == null) {

container = new HashMap<>();

}

container.put(key, data);

}

public Object getData(Object key) {

if (container == null) {

return null;

}

return container.get(key);

}

}

public static class MyRunnable implements Runnable {

@Override

public void run() {

Mythread curr = (Mythread) Thread.currentThread();

curr.putData("key1", (int) (Math.random() * 100D));

curr.putData("key2", Math.random() * 100D);

try {

Thread.sleep(2000);

} catch (InterruptedException e) {

}

System.out.println(curr.getData("key1"));

System.out.println(curr.getData("key2"));

}

}

public static void main(String[] args) {

MyRunnable sharedRunnableInstance = new MyRunnable();

Thread thread1 = new Mythread(sharedRunnableInstance);

Thread thread2 = new Mythread(sharedRunnableInstance);

thread1.start();

thread2.start();

}

}

/*

可能的运行结果:

45

11

4.537484778554357

27.90239993353697

*/

130282447fd42903084ce17e9fab2427.png

JDK中的实现

JDK中的实现类似于第二种方案,在Thread类中,有一个ThreadLocal.ThreadLocalMap类型的变量threadLocals,实现类似上面container的功能;其次,在ThreadLocal的get和set方法内部获取threadLocals来存放数据。threadLocals也不像上面的container,直接使用字符串做为键,而是使用ThreadLocal对象本身,不同的ThreadLocal对象刚好对应这不同的独立备份。

ThreadLocal如何防止内存泄漏

ThreadLocal中,获取到线程私有对象是通过Thread类持有的一个threadLocalMap(threadLocals),然后传入ThreadLocal当做key获取到对象的,这时候就有个问题,如果你在使用完ThreadLocal之后,将其置为null,这时候这个对象并不能被回收,因为他还有 ThreadLocalMap->entry->key的引用,直到该线程被销毁,但是这个线程很可能会被放到线程池中不会被销毁,这就产生了内存泄露,jdk是通过弱引用来解决的这个问题的,entry中对key的引用是弱引用,当你取消了ThreadLocal的强引用之后,他就只剩下一个弱引用了,所以也会被回收。

弱引用的使用,可参考这篇文章 http://www.cnblogs.com/dolphin0520/p/3784171.html

参考文章:

https://blog.csdn.net/u011983531/article/details/50833784

https://www.tuicool.com/articles/auE3A3e

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值