首先什么是ThreadLocal,threadlocal是一种解决多线程并发的方案。它的主体是Java类ThreadLocal。
多线程在处理数据时,常因为某个数据已经被其他线程改动,导致并发问题,ThreadLocal利用将数据存入只能由当前线程存取的一个数据副本,解决了线程并发的问题。
首先threadlocal不是一个线程,它更像是一个线程工具类,专门用来共享线程数据,注意我用的是共享,而不是传递线程数据,这部分有什么区别呢,往下看。
threadlocal的实现方式是操作每个线程都会有的一个ThreadLocalMap数据,这个数据在每一个Thread中。
源代码如下:
public classThread
implementsRunnable
{
/* 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=newThread(){
public voidrun(){
ThreadLocalUtil.setId(123L);
for(inti=0;i<3;i++)
{
LOG.info("test
A start="+ ThreadLocalUtil.getId());
}
//B线程在A线程内
Thread B=newThread(){
public voidrun(){
for(inti=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=newThread(){
public voidrun(){
ThreadLocalUtil.setInnerId(123L);
for(inti=0;i<3;i++)
{
LOG.info("test
A start="+ ThreadLocalUtil.getInnerId());
}
//B线程在A线程内
Thread B=newThread(){
public voidrun(){
for(inti=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 classThreadLocal<T>
{
public void
set(Tvalue)
{
Thread t=
Thread.currentThread();
ThreadLocalMap map=
getMap(t);
if(map!=
null)
map.set(this,value);
else
createMap(t,value);
}
public Tget() {
Thread t=
Thread.currentThread();
ThreadLocalMap map=
getMap(t);
if(map!=
null) {
ThreadLocalMap.Entry e=
map.getEntry(this);
if(e!=
null)
return(T)e.value;
}
returnsetInitialValue();
}
}
简单贴两块代码,有兴趣的自行查找吧。
这样关于threadlocal的基本用法就介绍完了。
下面说说thread中start和run的区别。
先说结论,start会启动一个新线程,run会在原有的线程上运行。
下面来验证:
Thread A=newThread(){
public voidrun(){
ThreadLocalUtil.setId(123);
for(inti=0;i<3;i++)
{
LOG.info("test
A start="+ ThreadLocalUtil.getId());
}
}
};
Thread B=newThread(){
public voidrun(){
//注意,Thread
B没有初始化过threadLocalMap,但是run方法一样可以取出数据并使用
// ThreadLocalUtil.setId(123L);
for(inti=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来存取数据,应用于各个不同的业务模型中,甚至跳过某些业务去应用到隔级,或者隔业务的对象处理中。这种使用方式是不适当的,紧耦合的,甚至会无法进行正常的测试或独立交付。像这种数据传递应该作为方法或者其他途径的显示声明来进行。
而数据分享,是提供一个数据,表明当前线程状态,或者提供公共的数据看板。这样才符合数据分享,也是解耦合的一种方式。