ThreadLocal使用,Thread的start和run的区别,等一些琐碎的东西

本文深入解析了ThreadLocal的工作原理,探讨了其如何通过为每个线程提供独立的数据副本来解决多线程并发问题,并对比了ThreadLocal与InheritableThreadLocal的不同之处。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先什么是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来存取数据,应用于各个不同的业务模型中,甚至跳过某些业务去应用到隔级,或者隔业务的对象处理中。这种使用方式是不适当的,紧耦合的,甚至会无法进行正常的测试或独立交付。像这种数据传递应该作为方法或者其他途径的显示声明来进行。
而数据分享,是提供一个数据,表明当前线程状态,或者提供公共的数据看板。这样才符合数据分享,也是解耦合的一种方式。







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值