理解 ThreadLocal

理解 ThreadLocal

原来就用过ThreadLocal,但是不是特别的明白,今天又看了一下。

摘自《Spring 揭密》 王福强著 人民邮电出版社

1 ThreadLocal的背景

单单从程序层面来看,我们编写的代码实际上是在管理系统中各个对象的相关状态,如果不能对各个对象的状态的访问进行合理的管理,对象的状态将被破坏,进而导致系统的不正常运行,特别是多线程环境下,多个线程可能对系统之中的单一的或者多个对象状态进行访问,如果不能保证在此期间的线程安全,将会把系统带向崩溃的边缘。

为了保证整个应用程序的线程安全,我们可以采用多种方式,不过在此之前,我们不妨先看看打架和我们管理线程安全问题有何相似之处。现在你就是系统中的一个对象,假设某一天你出门在外遇到三个歹徒,你毫无退路,只能一搏,你会采用什么样的策略来保证你的人身安全(线程安全)呢?

策略一,你是一个练家子,而且他人身边无他人帮助,你只能同事对付三个歹徒,为了不受伤害,你尽量保证每一时刻只对付一名歹徒,以便最终能全身撤退,如果把三名对你的攻击顺序比作三个线程的话,你实际上是在用同步(synchronization)的方式来管理线程对你的访问。

是同同步方式来管理多个线程对对象状态的访问以保证应用程序的线程安全是最常用的方式,不过,你也看到了,你需要很辛苦的对待,稍有不慎,就可能受伤,所以这是我们就在想,我要是孙悟空该多好。

“但见那孙猴子揪一撮猴毛一吹,片刻化为多个分身”,“小的们,给我上”。现在我们再也不用苦熬了,让一个分身对付一个歹徒,让他们在各自的线程内自己折腾去吧!不用一对三,当然也就不可能破坏到我的完好状态了!

这就是策略二,通过避免对象的共享,同样达到了线程安全的目的,你想啊,都在各自的线程内跟各自的分身折腾去了,自然不会需要同步对单一资源的访问了,ThreadLocal的出现,实际上就是帮助我们以策略二的方式来管理程序的线程安全,只要当前环境允许,能不共享的尽量不共享,反而更容易管理应用程序的线程安全。

综上来说,同步和ThreadLocal在横向上可能没有任何的关系,但从纵向来看,他们实际上都服务于一个目的,那就是帮助我们实现应用程序的线程安全,另外,说ThreadLocal不是用来解决多线程环境下对象共享问题的,也就更好解释了,ThreadLocal的目的是通过避免对象的共享来保证应用程序的线程安全,共享对象是ThreadLocal尽量避免的,如果要管理的对象非要共享,ThreadLocal自然不会理会这回事了。

2 ThreadLocal的实现

虽然是通过ThreadLocal来设置特定于各个线程的数据资源,单ThreadLocal自身不会保存这些特定的数据资源,因为数据资源特定于线程的,自然是由每个线程自己来管理了,
每个Thread类都有一个ThreadLocal.ThreadLocalMap类型的名为threadLocals的实例变量,它就是保持那些通过ThreadLocal设置给当前线程的数据资源的地方,当通过ThreadLocal
的set(data)方法来设置数据的时候,ThreadLocal会首先获取当前线程的引用,然后通过该引用获取当前线程持有的threadLocals,最后,以当前的ThreadLocal作为key,将要设置的
数据设置为当前线程,如下所示:
Thread thread = Thread.currentThread();
ThreadLocalMap threadLocalMap = thread.threadLocals;
...
threadLocalMap.set(this,obj);

而至于余下的get()之类的方法,基本上也是相通的道理,都是首先取得当前线程,然后根据每个方法的语义,对当前线程所持有的threadLocals中的数据进行操作。实际上,ThreadLocal就好像是一个窗口,通过这个窗口,我们可以将特定于线程的数据资源绑定到单签线程,也可以通过这个窗口获取绑定的数据资源,当然,更可以接触之前绑定到当前线程的数据资源,在整个线程的生命周期内,我们都可以通过ThreadLocal这个窗口与当前线程打交道。

(大家可以参考JDK中ThreadLocal和Thread类的源码!)

为了更好的理解ThreadLocal和Thread的关系,我们不妨设想一下城市的公交系统:
城市中的各条公交线路就好像我们系统中的那一个个线程,在各条公交线路上(比如48),会有相应的公交车辆(一堆48),这些车辆就好像Thread和ThreadLocals。用来运送特定于该条线路的
乘客(数据资源)。为了乘客可以乘车或者下车,各条公交线都会设置多个乘车点(Bus Stop),而这些乘车点实际上就是ThreadLocal(比如向阳楼),虽然同一个乘车点可能会有多条公交线路共用,但是同一时间,
乘客只会搭乘他要乘坐并且当前经过的公交车,这与ThreadLocal和Thread的关系是类似的,虽然同一个Threadlocal可以为多个线程绑定资源,但只会将数据资源绑定到当前的线程。

是不是有点晕?根据这个公交车的例子,我自己写了个程序。

BusThreadlocal.java

package com.edgar;


/**
* 模拟公交车站的Threadlocal
* @author Edgar
*
*/
public class BusThreadlocal extends ThreadLocal<String> {
private String busStopName; //车站的名字

public BusThreadlocal(String busStopName) {
   super();
   this.busStopName = busStopName;
}

public String getBusStopName() {
   return busStopName;
}

public void setBusStopName(String busStopName) {
   this.busStopName = busStopName;
}
}

Bus.java

package com.edgar;

import java.util.Random;

//公交车线程
public class Bus implements Runnable {

private String name;       //公交车的名字

//这里假设每个公交车都会经过五个站点,结合本例,每个站点都是一个ThreadLocal,一

//个站点对应一个Threadlocal对象
private BusThreadlocal stop1;  
private BusThreadlocal stop2;
private BusThreadlocal stop3;
private BusThreadlocal stop4;
private BusThreadlocal stop5;

public Bus(String name, BusThreadlocal stop1, BusThreadlocal stop2,
    BusThreadlocal stop3, BusThreadlocal stop4, BusThreadlocal stop5) {
   super();
   this.name = name;
   this.stop1 = stop1;
   this.stop2 = stop2;
   this.stop3 = stop3;
   this.stop4 = stop4;
   this.stop5 = stop5;
}

@Override
public void run() {
   // TODO Auto-generated method stub
   Random rand = new Random();

   String str = name + ": " + stop1.getBusStopName() + " 到站了。"
     + rand.nextInt(10) + "人下车";
   System.out.println(str);
   stop1.set(str);   //绑定当当前线程
   str = name + ": " + stop2.getBusStopName() + " 到站了。" + rand.nextInt(10)
     + "人下车";
   System.out.println(str);
   stop2.set(str);
   str = name + ": " + stop3.getBusStopName() + " 到站了。" + rand.nextInt(10)
     + "人下车";
   System.out.println(str);
   stop3.set(str);
   str = name + ": " + stop4.getBusStopName() + " 到站了。" + rand.nextInt(10)
     + "人下车";
   System.out.println(str);
   stop4.set(str);
   str = name + ": " + stop5.getBusStopName() + " 到站了。" + rand.nextInt(10)
     + "人下车";
   System.out.println(str);
   stop5.set(str);

   System.out.println("已经到终点站了。");

   System.out.println(name + "   经过的站点:--------------------");
   System.out.println(stop1.get()); //取的绑定的值,和set的时候是一样的。
   System.out.println(stop2.get());
   System.out.println(stop3.get());
   System.out.println(stop4.get());
   System.out.println(stop5.get());
}

}
Test.java

package com.edgar;

public class Test {

/**
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
   // TODO Auto-generated method stub
   BusThreadlocal l1 = new BusThreadlocal("咸阳北路");
   BusThreadlocal l2 = new BusThreadlocal("洪湖里");
   BusThreadlocal l3 = new BusThreadlocal("长虹公园");
   BusThreadlocal l4 = new BusThreadlocal("天拖大楼");
   BusThreadlocal l5 = new BusThreadlocal("天津图书馆");
  
  
   BusThreadlocal l6 = new BusThreadlocal("文江东里");
   BusThreadlocal l7 = new BusThreadlocal("王顶堤");
  
  
   Bus n48_01 = new Bus("第一个48路",l1,l2,l3,l4,l5);
   //48和911有三个相同的站点
   Bus n911_01 = new Bus("第一个911路",l6,l2,l3,l4,l7);
   Bus n911_02 = new Bus("第二个911路",l6,l2,l3,l4,l7);
  
   new Thread(n48_01).start();
   new Thread(n911_01).start();
   new Thread(n911_02).start();
}

}

class NO48 implements Runnable{
ThreadLocal<String> threadLocal = new ThreadLocal<String>();
@Override
public void run() {
   // TODO Auto-generated method stub
  
   threadLocal.set(" set value in MyThread.run()+ 当前线程为: "+Thread.currentThread().getName());
   System.out.println(threadLocal.get());
   //System.out.println("done");
}
}

运行结果:

第二个911路: 文江东里 到站了。0人下车
第二个911路: 洪湖里 到站了。9人下车
第二个911路: 长虹公园 到站了。0人下车
第二个911路: 天拖大楼 到站了。5人下车
第二个911路: 王顶堤 到站了。5人下车
已经到终点站了。
第二个911路   经过的站点:--------------------
第二个911路: 文江东里 到站了。0人下车
第二个911路: 洪湖里 到站了。9人下车
第二个911路: 长虹公园 到站了。0人下车
第二个911路: 天拖大楼 到站了。5人下车
第二个911路: 王顶堤 到站了。5人下车
第一个48路: 咸阳北路 到站了。1人下车
第一个48路: 洪湖里 到站了。3人下车
第一个48路: 长虹公园 到站了。9人下车
第一个48路: 天拖大楼 到站了。0人下车
第一个48路: 天津图书馆 到站了。7人下车
已经到终点站了。
第一个48路   经过的站点:--------------------
第一个48路: 咸阳北路 到站了。1人下车
第一个48路: 洪湖里 到站了。3人下车
第一个48路: 长虹公园 到站了。9人下车
第一个48路: 天拖大楼 到站了。0人下车
第一个48路: 天津图书馆 到站了。7人下车
第一个911路: 文江东里 到站了。8人下车
第一个911路: 洪湖里 到站了。4人下车
第一个911路: 长虹公园 到站了。4人下车
第一个911路: 天拖大楼 到站了。7人下车
第一个911路: 王顶堤 到站了。0人下车
已经到终点站了。
第一个911路   经过的站点:--------------------
第一个911路: 文江东里 到站了。8人下车
第一个911路: 洪湖里 到站了。4人下车
第一个911路: 长虹公园 到站了。4人下车
第一个911路: 天拖大楼 到站了。7人下车
第一个911路: 王顶堤 到站了。0人下车

可以看到,一个Threadlocal可以同时使用在多个线程上。并且结果是正确的。

3 Threadlocal的应用场景

基本上,我们可以从2个方面来看待并灵活使用Threadlocal。1 从横向上看,我们更注重于Threadlocal横跨多个线程的能力,这当然是Threadlocal最初的目的,为了
以更加方便的方式来管理应用程序的安全,Threadlocal干脆将没有必要的对象不共享,直接为每个线程分配一份各自特定的数据资源。

2 从纵向上来看,我们着眼于Threadlocal在单一线程内可以发挥的能力,通过Threadlocal设置的特定于各个线程的数据资源,可以随着所在线程的执行过程“随波逐流”。

当然,两个方面并不是独立的,更多的时候他们是相互依存,紧密耦合的。

例子:我精简了一下,Connection对象是有状态并且非线程安全的,为了保证多个线程使用Connection进行数据访问过程中的安全,我们通过Threadlocal为每个线程分配了一个他们各自持有的Connection,从而避免了对单一Connection资源的争用。

原文后面还有一些,在这就不介绍了。。。

PS:在博客中摘抄别人著作内容可能会造成侵权,在此再次强调一下。。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值