首先什么是ThreadLocal,threadlocal是一种解决多线程并发的方案。它的主体是Java类ThreadLocal。
多线程在处理数据时,常因为某个数据已经被其他线程改动,导致并发问题,ThreadLocal利用将数据存入只能由当前线程存取的一个数据副本,解决了线程并发的问题。
首先threadlocal不是一个线程,它更像是一个线程工具类,专门用来共享线程数据,注意我用的是共享,而不是传递线程数据,这部分有什么区别呢,往下看。
threadlocal的实现方式是操作每个线程都会有的一个ThreadLocalMap数据,这个数据在每一个Thread中。
源代码如下:
public class
Thread
implements
Runnable
{
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap
threadLocals
=
null
;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap
inheritableThreadLocals
=
null
;
}
可以看到,ThreadLocal实际上是操作并使用了当前Thead的ThreadLocalMap副本,每个线程独立使用属于自己的数据副本,这样避免了互相冲突,保障了线程中的数据安全。
根据上面的的ThreadLocalMap,可以看到ThreadLocalMap跟随当前线程存在,不同线程操作不同的ThreadLocalMap,根据源码可以看到存在两种ThreadLocalMap,我们当前这篇通常指的是threadLocals这个map,而另一个inheritableThreadLocals,从它的名字可以看出来,它虽然是一种标准的ThreadlocalMap但是它是可以被子线程继承使用的一种threadLocalMap。
这点我们来证明一下:
Thread A
=
new
Thread
(){
public void
run
(){
ThreadLocalUtil
.
setId
(
123L
);
for
(
int
i
=
0
;
i
<
3
;
i
++) {
LOG
.
info
(
"test A start="
+
ThreadLocalUtil
.
getId
());
}
//B
线程在
A
线程内
Thread B
=
new
Thread
(){
public void
run
(){
for
(
int
i
=
0
;
i
<
3
;
i
++) {
LOG
.
info
(
"test B start="
+
ThreadLocalUtil
.
getId
());
}
}
};
B
.
start
();
}
};
A
.
start
();
如带码,B在A内,如果使用上述代码,用普通的threadlocal,则会报空指针异常,因为B的local还么初始化。
2017-06-08 10:16:58 [INFO ] testspr.TestThreadLocalMap$1.run(TestThreadLocalMap.java:19) : test A start=124
Exception in thread "Thread-1" java.lang.NullPointerException
2017-06-08 10:16:58 [INFO ] testspr.TestThreadLocalMap$1.run(TestThreadLocalMap.java:19) : test A start=125
at testspr.threadutil.ThreadLocalUtil.getId(ThreadLocalUtil.java:16)
2017-06-08 10:16:58 [INFO ] testspr.TestThreadLocalMap$1.run(TestThreadLocalMap.java:19) : test A start=126
at testspr.TestThreadLocalMap$1$1.run(TestThreadLocalMap.java:26)
Process finished with exit code 0
在修改成InHeritableThreadLocalMap之后
Thread A
=
new
Thread
(){
public void
run
(){
ThreadLocalUtil
.
setInnerId
(
123L
);
for
(
int
i
=
0
;
i
<
3
;
i
++) {
LOG
.
info
(
"test A start="
+
ThreadLocalUtil
.
getInnerId
());
}
//B
线程在
A
线程内
Thread B
=
new
Thread
(){
public void
run
(){
for
(
int
i
=
0
;
i
<
3
;
i
++) {
LOG
.
info
(
"test B start="
+
ThreadLocalUtil
.
getInnerId
());
}
}
};
B
.
start
();
}
};
A
.
start
();
运行成功:
2017-06-08 10:17:33 [INFO ] testspr.TestThreadLocalMap$1.run(TestThreadLocalMap.java:19) : test A start=124
2017-06-08 10:17:33 [INFO ] testspr.TestThreadLocalMap$1.run(TestThreadLocalMap.java:19) : test A start=125
2017-06-08 10:17:33 [INFO ] testspr.TestThreadLocalMap$1.run(TestThreadLocalMap.java:19) : test A start=126
2017-06-08 10:17:33 [INFO ] testspr.TestThreadLocalMap$1$1.run(TestThreadLocalMap.java:26) : test B start=127
2017-06-08 10:17:33 [INFO ] testspr.TestThreadLocalMap$1$1.run(TestThreadLocalMap.java:26) : test B start=128
2017-06-08 10:17:33 [INFO ] testspr.TestThreadLocalMap$1$1.run(TestThreadLocalMap.java:26) : test B start=129
证明说法是对的inheritableMap支持子线程取父线程的threadloaclmap
刚才有一个疑问,为什么子线程取不到threadlocal时候会报空指针?难道?
没错threadlocalmap的初始化不是tread类自己控制的,而是ThreadLocal类控制的。
public class
ThreadLocal
<
T
> {
public void
set
(
T
value
) {
Thread t
=
Thread
.
currentThread
();
ThreadLocalMap map
=
getMap
(
t
);
if
(
map
!=
null
)
map
.
set
(
this
,
value
);
else
createMap
(
t
,
value
);
}
public
T
get
() {
Thread t
=
Thread
.
currentThread
();
ThreadLocalMap map
=
getMap
(
t
);
if
(
map
!=
null
) {
ThreadLocalMap.Entry e
=
map
.
getEntry
(
this
);
if
(
e
!=
null
)
return
(
T
)
e
.
value
;
}
return
setInitialValue
();
}
}
简单贴两块代码,有兴趣的自行查找吧。
这样关于threadlocal的基本用法就介绍完了。
下面说说thread中start和run的区别。
先说结论,start会启动一个新线程,run会在原有的线程上运行。
下面来验证:
Thread A
=
new
Thread
(){
public void
run
(){
ThreadLocalUtil
.
setId
(
123
);
for
(
int
i
=
0
;
i
<
3
;
i
++) {
LOG
.
info
(
"test A start="
+
ThreadLocalUtil
.
getId
());
}
}
};
Thread B
=
new
Thread
(){
public void
run
(){
//
注意,
Thread B
没有初始化过
threadLocalMap
,但是
run
方法一样可以取出数据并使用
// ThreadLocalUtil.setId(123L);
for
(
int
i
=
0
;
i
<
3
;
i
++) {
LOG
.
info
(
"test B start="
+
ThreadLocalUtil
.
getId
());
}
}
};
// A.start();
// B.start();
A
.
run
();
B
.
run
();
当使用start时候,会报空指针异常,因为thread B没有初始化。
当run时候可以正常运行,因为run方法实际上没有创建新线程,这样threadlocalmap初始化过就可以使用了。
最后来说一下,threadlocal的使用。
对struts,hibnate这样的框架来说,threadlocal是一种实现某种业务常用的手段。
比如使用threadlocal去记录一些标志,或者用其去管理一些session数据。
但是作为一种线程参数,threadlocal实际的使用应该是面向“数据分享”而不是“数据传递”
这两部分区别在于,数据传递是用threadlocal来存取数据,应用于各个不同的业务模型中,甚至跳过某些业务去应用到隔级,或者隔业务的对象处理中。这种使用方式是不适当的,紧耦合的,甚至会无法进行正常的测试或独立交付。像这种数据传递应该作为方法或者其他途径的显示声明来进行。
而数据分享,是提供一个数据,表明当前线程状态,或者提供公共的数据看板。这样才符合数据分享,也是解耦合的一种方式。