ThreadLocal解读 Wed, Mar 17, 2021 从Set方法入手 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void set ( T value ) {
Thread t = Thread . currentThread ();
ThreadLocalMap map = getMap ( t );
if ( map != null )
map . set ( this , value );
else
createMap ( t , value );
}
void createMap ( Thread t , T firstValue ) {
t . threadLocals = new ThreadLocalMap ( this , firstValue );
}
ThreadLocalMap getMap ( Thread t ) {
return t . threadLocals ;
}
从这里可以看出,threadLocal的核心其实是ThreadLocalMap
对象。
set
方法的作用是初始化内部的ThreadLocalMap
,并将元素放入ThreadLocalMap
中暂存,当前的ThreadLocal对象为key。
然后关注createMap()
方法:
1
2
3
4
5
6
7
8
9
10
11
void createMap ( Thread t , T firstValue ) {
t . threadLocals = new ThreadLocalMap ( this , firstValue );
}
ThreadLocalMap ( ThreadLocal <?> firstKey , Object firstValue ) {
table = new Entry [ INITIAL_CAPACITY ];
int i = firstKey . threadLocalHashCode & ( INITIAL_CAPACITY - 1 );
table [ i ] = new Entry ( firstKey , firstValue );
size = 1 ;
setThreshold ( INITIAL_CAPACITY );
}
每个线程中都有一个threadLocals
对象,每次调用createMap()
方法时都会给线程的threadLocals
创建一个新的ThreadLocalMap
,那么每次调用set()
方法,其实是set给当前线程的ThreadLocalMap
。
结论:ThreadLocal
的set
方法其实是把值存到了当前线程内部的threadLocals
,而threadLocals
是一个ThreadLocalMap
类型的map(map嵌套),key就是当前的ThreadLocal
。
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
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 ();
}
//ThreadLocal内部方法
private Entry getEntry ( ThreadLocal <?> key ) {
int i = key . threadLocalHashCode & ( table . length - 1 );
Entry e = table [ i ];
if ( e != null && e . get () == key )
return e ;
else
return getEntryAfterMiss ( key , i , e );
}
private T setInitialValue () {
T value = initialValue ();
Thread t = Thread . currentThread ();
ThreadLocalMap map = getMap ( t );
if ( map != null )
map . set ( this , value );
else
createMap ( t , value );
return value ;
}
ThreadLocalMap ThreadLocalMap其实是个简化的HashMap,内部是个Entry
类型的数组。
1
2
3
4
5
6
7
8
9
static class Entry extends WeakReference < ThreadLocal <?>> {
/** The value associated with this ThreadLocal. */
Object value ;
Entry ( ThreadLocal <?> k , Object v ) {
super ( k );
value = v ;
}
}
真的线程安全? 大部分博客都说ThreadLocal
是把变量在每个线程生成变量副本,线程只能操作属于自己的副本,从而不会相互影响,并且线程安全。
但是我翻来覆去的看代码,也没有看到打破引用的代码啊,没有clone
,没有new
,那怎么能叫副本呢?那应该不管怎么赋值,都是共享变量的引用才对啊。那么在不同线程中操作同一共享变量,又没加锁,还谈什么线程安全?
那就写个例子来印证一下:
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
public class MainActivity extends AppCompatActivity {
//随便创建一个线程池
private final ThreadPoolExecutor tpe = new ThreadPoolExecutor ( 8 , 8 , 1 , TimeUnit . SECONDS , new LinkedBlockingDeque < Runnable >());
@Override
protected void onCreate ( Bundle savedInstanceState ) {
super . onCreate ( savedInstanceState );
setContentView ( R . layout . activity_main );
}
//点击按钮后让线程池执行两个任务
Data data ;
public void start ( View v ) {
data = new Data ();
tpe . execute ( new MyRunnable1 ( data ));
tpe . execute ( new MyRunnable2 ( data ));
}
//等10秒之后点第二个按钮展示结果
public void toastResult ( View v ) {
Log . d ( "我试试" , "toastResult: " + data );
Toast . makeText ( this , "toastResult: " + data , Toast . LENGTH_SHORT ). show ();
}
//任务1
static class MyRunnable1 implements Runnable {
private final ThreadLocal < Data > mThreadLocal = new ThreadLocal <>();
Data data ;
MyRunnable1 ( Data data ) {
this . data = data ;
}
@Override
public void run () {
mThreadLocal . set ( data );
mThreadLocal . get (). name = "李四" ;
mThreadLocal . get (). age = 10 ;
try {
Thread . sleep ( 10 * 1000 );
Log . d ( "我试试" , "线程1: " + data );
} catch ( InterruptedException e ) {
e . printStackTrace ();
}
}
}
// 任务2
static class MyRunnable2 implements Runnable {
private ThreadLocal < Data > mThreadLocal = new ThreadLocal <>();
Data data ;
MyRunnable2 ( Data data ) {
this . data = data ;
}
@Override
public void run () {
try {
Thread . sleep ( 3000 );
Log . d ( "我试试" , "线程2:" + data );
mThreadLocal . set ( data );
mThreadLocal . get (). name = "王五" ;
mThreadLocal . get (). age = 20 ;
} catch ( InterruptedException e ) {
e . printStackTrace ();
}
}
}
static class Data {
int age = 1 ;
String name = "张三" ;
Data ( int age , String name ) {
this . age = age ;
this . name = name ;
}
Data () {
}
@NonNull
@Override
public String toString () {
return this . name + this . age ;
}
}
}
上面代码的逻辑:创建一个变量,然后分别在两个线程中用set进各自的ThreadLocal,然后get各自的变量副本,修改值,打印当前值。最后等个20秒以后点一下获取结果按钮。
但是当我最后获取结果的时候,发现原始的Data数据变了,变成了任务2中的赋值结果!而且在两个任务执行期间,threadLocal.get()
也能看到其他变量对data的修改结果,果然跟我的猜测不错!
通过印证,我发现ThreadLocal并不能解决线程安全问题,也并不是什么共享变量的副本。他的作用只是把数据与线程绑定,而且通过弱引用的方式避免线程作用域内的对象内存泄露。
为什么Looper采用ThreadLocal? 因为一般我们操作线程的变量,都要在线程里面去改它。我们可能new一个Thread或者new一个Runnable,通过构造方法或者在run方法里面去写变量赋值。那么问题来了,如果你拿不到线程,不能修改线程呢?比如ActivityThread。
而Looper的源码里面没有一句操作线程的代码,而是巧用了ThreadLocal完成了Looper自己与线程的绑定!!!
所以ThreadLocal的应用场景是:不需要在线程中做操作,只需要把变量与线程绑定时就用ThreadLocal!ThreadLocal就是个中介者模式!