博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ThreadLocal用法详解和原理(转)
阅读量:4663 次
发布时间:2019-06-09

本文共 7166 字,大约阅读时间需要 23 分钟。

本文转自https://www.cnblogs.com/coshaho/p/5127135.html 感谢作者

一、用法

ThreadLocal用于保存某个线程共享变量:对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。

1、ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。

2、ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。

3、ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。

4、ThreadLocal.initialValue: ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package 
com.coshaho.reflect;
 
/**
 
* ThreadLocal用法
 
* @author coshaho
 
*
 
*/
public 
class 
MyThreadLocal
{
    
private 
static 
final 
ThreadLocal<Object> threadLocal = 
new 
ThreadLocal<Object>(){
        
/**
         
* ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值
         
*/
        
@Override
        
protected 
Object initialValue()
        
{
            
System.out.println(
"调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!"
);
            
return 
null
;
        
}
    
};
     
    
public 
static 
void 
main(String[] args)
    
{
        
new 
Thread(
new 
MyIntegerTask(
"IntegerTask1"
)).start();
        
new 
Thread(
new 
MyStringTask(
"StringTask1"
)).start();
        
new 
Thread(
new 
MyIntegerTask(
"IntegerTask2"
)).start();
        
new 
Thread(
new 
MyStringTask(
"StringTask2"
)).start();
    
}
     
    
public 
static 
class 
MyIntegerTask 
implements 
Runnable
    
{
        
private 
String name;
         
        
MyIntegerTask(String name)
        
{
            
this
.name = name;
        
}
 
        
@Override
        
public 
void 
run()
        
{
            
for
(
int 
i = 
0
; i < 
5
; i++)
            
{
                
// ThreadLocal.get方法获取线程变量
                
if
(
null 
== MyThreadLocal.threadLocal.get())
                
{
                    
// ThreadLocal.et方法设置线程变量
                    
MyThreadLocal.threadLocal.set(
0
);
                    
System.out.println(
"线程" 
+ name + 
": 0"
);
                
}
                
else
                
{
                    
int 
num = (Integer)MyThreadLocal.threadLocal.get();
                    
MyThreadLocal.threadLocal.set(num + 
1
);
                    
System.out.println(
"线程" 
+ name + 
": " 
+ MyThreadLocal.threadLocal.get());
                    
if
(i == 
3
)
                    
{
                        
MyThreadLocal.threadLocal.remove();
                    
}
                
}
                
try
                
{
                    
Thread.sleep(
1000
);
                
}
                
catch 
(InterruptedException e)
                
{
                    
e.printStackTrace();
                
}
            
}  
        
}
         
    
}
     
    
public 
static 
class 
MyStringTask 
implements 
Runnable
    
{
        
private 
String name;
         
        
MyStringTask(String name)
        
{
            
this
.name = name;
        
}
 
        
@Override
        
public 
void 
run()
        
{
            
for
(
int 
i = 
0
; i < 
5
; i++)
            
{
                
if
(
null 
== MyThreadLocal.threadLocal.get())
                
{
                    
MyThreadLocal.threadLocal.set(
"a"
);
                    
System.out.println(
"线程" 
+ name + 
": a"
);
                
}
                
else
                
{
                    
String str = (String)MyThreadLocal.threadLocal.get();
                    
MyThreadLocal.threadLocal.set(str + 
"a"
);
                    
System.out.println(
"线程" 
+ name + 
": " 
+ MyThreadLocal.threadLocal.get());
                
}
                
try
                
{
                    
Thread.sleep(
800
);
                
}
                
catch 
(InterruptedException e)
                
{
                    
e.printStackTrace();
                
}
            
}
        
}
         
    
}
<strong>}
</strong>

运行结果如下:

调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!
线程IntegerTask1:
0
调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!
线程IntegerTask2:
0
调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!
调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!
线程StringTask1: a
线程StringTask2: a
线程StringTask1: aa
线程StringTask2: aa
线程IntegerTask1:
1
线程IntegerTask2:
1
线程StringTask1: aaa
线程StringTask2: aaa
线程IntegerTask2:
2
线程IntegerTask1:
2
线程StringTask2: aaaa
线程StringTask1: aaaa
线程IntegerTask2:
3
线程IntegerTask1:
3
线程StringTask1: aaaaa
线程StringTask2: aaaaa
调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!
线程IntegerTask2:
0
调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!
线程IntegerTask1:
0

二、原理

线程共享变量缓存如下:

Thread.ThreadLocalMap<ThreadLocalObject>;

1、Thread: 当前线程,可以通过Thread.currentThread()获取。

2、ThreadLocal:我们的static ThreadLocal变量。

3、Object: 当前线程共享变量。

我们调用ThreadLocal.get方法时,实际上是从当前线程中获取ThreadLocalMap<ThreadLocalObject>,然后根据当前ThreadLocal获取当前线程共享变量Object。

ThreadLocal.set,ThreadLocal.remove实际上是同样的道理。

 

关于ThreadLocalMap<ThreadLocalObject>弱引用问题:

当线程没有结束,但是ThreadLocal已经被回收,则可能导致线程中存在ThreadLocalMap<nullObject>的键值对,造成内存泄露。(ThreadLocal被回收,ThreadLocal关联的线程共享变量还存在)。

虽然ThreadLocal的get,set方法可以清除ThreadLocalMap中key为null的value,但是get,set方法在内存泄露后并不会必然调用,所以为了防止此类情况的出现,我们有两种手段。

1、使用完线程共享变量后,显示调用ThreadLocalMap.remove方法清除线程共享变量;

2、JDK建议ThreadLocal定义为private static,这样ThreadLocal的弱引用问题则不存在了。

 

 

-------------------------------

基本原理

线程本地变量是和线程相关的变量,一个线程则一份数据。我们通过ThreadLocal保存的数据最终是保存在Thread类的ThreadLocalMap threadLocals变量中。ThreadlocalMap是一个Map结构,其中key为我们声明的ThreadLocal对象,value即为我们使用ThreadLocal保存的线程本地变量.

当我们调用ThreadLocal变量set方法时,那么为将TheadLocal作为key,set方法的参数做为value保存在当前线程的threadLocals中.调用get方法时类似,调用get方法时,会去Thread的threadLocals中去寻找key为ThreadLocal 变量的值

源码如下:

//Thread.threadLocals变量声明/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class.  */ThreadLocal.ThreadLocalMap threadLocals = null;// ThreadLocal set get方法/** * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the { @link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of * this thread-local. */ public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t);// getMap方法即去获取当前线程的ThreadLocalMap变量。 if (map != null) map.set(this, value);//以this(ThreadLocal本身)为Key,参数value为值进行保存 else createMap(t, value); } /** * Get the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @return the map */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; } /** * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the { @link #initialValue} method. * * @return the current thread's value of this thread-local */ 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(); }

下面是测试代码:

static ThreadLocal
stringThreadLocal = new ThreadLocal<>();@Testpublic void test01(){ Thread thread1 = new Thread(){ @Override public void run() { stringThreadLocal.set("threadName===>"+Thread.currentThread().getName()); System.out.println(this.getName()+" thread get the value:"+stringThreadLocal.get()); } }; Thread thread2 = new Thread(){ @Override public void run() { stringThreadLocal.set("threadName===>"+Thread.currentThread().getName()); System.out.println(this.getName()+" thread get the value:"+stringThreadLocal.get()); } }; Thread thread3 = new Thread(){ @Override public void run() { stringThreadLocal.set("threadName===>"+Thread.currentThread().getName()); System.out.println(this.getName()+" thread get the value:"+stringThreadLocal.get()); } }; thread1.start(); thread2.start(); thread3.start(); System.out.println("main线程调用set方法之前:"+stringThreadLocal.get()); stringThreadLocal.set("main 线程set的值"); System.out.println("main线程调用set方法之后:"+stringThreadLocal.get()); }

可以看到不同线程设置的值在该线程是能够正确的取到。由于Thread的threadLocals变量只能在Thread所在的包下才能够访问,因此不能对该变量进行直接访问以验证设置的值在Thread.currentThread对象里面。但如果你调试以上代码,设置值之后访问Thread.currentThread.threadLocals会看到之前设置的值。其中key为声明的ThreadLocal对象。

转载于:https://www.cnblogs.com/panxuejun/p/8616943.html

你可能感兴趣的文章
微信二维码生成
查看>>
linux中 ll 和ls 区别
查看>>
有关js中能否使用equals来判断相等的问题
查看>>
(十八)多线程
查看>>
bzoj4580: [Usaco2016 Open]248
查看>>
HTML5 VS. Flash&Flex? – 浅谈Flash/Flex/HTML 5技术选型
查看>>
响应者链条
查看>>
基于定位的社交应用Foursquare开源网址(wp7)
查看>>
机电传动控制读书笔记二(书本内容)
查看>>
Address already in use: JVM_Bind<null>:8080错误的解决办法
查看>>
Vue子组件监听事件中传递参数的方法
查看>>
面向对象的几种方法详解(后)
查看>>
年龄问题
查看>>
winform自动更新并实现文件的批量异步下载
查看>>
UVA 301 Transportation
查看>>
MYSQL的常用命令和增删改查语句和数据类型!
查看>>
再回首数据结构—红黑树(一)
查看>>
界面设计规范(转)
查看>>
js与jquery混用问题
查看>>
可空类型 Nullable<T>
查看>>