深入理解Java的ThreadLocal

       Android中的多线程并发场景,通常是通过线程同步的方式去保证线程安全。对于共享资源,需要通过syncronized或者Lock等线程同步方法实现资源的互斥访问,才能确保数据访问的正确性。

       今天要介绍的ThreadLocal为解决多线程并发问题提供了一种新思路。想必熟悉Java的同学对ThreadLocal并不陌生,今天我们就一起来探讨ThreadLocal。

       本文将从以下几方面对ThreadLocal进行讲解:

 

  • ThreadLocal介绍
  • 源码角度分析ThreadLocal的原理
  • ThreadLocal的应用

 

 

ThreadLocal介绍

       首先,ThreadLocal是什么?它的名字看起来有点奇怪,其实,它是用来存放线程局部变量的容器,也许把它命名为ThreadLocalVar更为合适。ThreadLocal的功能其实非常简单,就是为每一个使用该变量的线程提供一份变量的副本,每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。

       从线程的角度看,ThreadLocal就像是线程的本地变量一样,线程之间相互独立。通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个线程绑定了私有的本地实例存取空间,从而为多线程中的并发访问问题提供了一种隔离机制。

       从时间和空间的角度来说,对于多线程资源共享问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者提供变量,让不同的线程互斥访问,而后者为每一个线程都提供了一份变量,可以同时访问而互不影响。也就是说,如果你定义了一个属性,而你并不需要在不同的线程中共享这个属性,只需要它在各个线程中独立的变化且互不影响,ThreadLocal会是一个不错的选择。

 

源码角度分析ThreadLocal原理

 

       为了搞清楚ThreadLocal的原理,我们来看一下ThreadLocal的源码,通过源码分析一下它是怎么实现线程本地变量的功能。

       ThreadLocal有一个关键内部类ThreadLocalMap,它是线程真正用来保存本地变量的容器。每一个线程都会有单独的ThreadLocalMap实例,它保存了这个线程所有的本地变量。接下来我们就分析一下ThreadLocal的两个最主要的get()和set()方法。

       ThreadLocal的set()方法用来设置当前线程中变量的副本,get()方法用来获取ThreadLocal在当前线程中保存的变量副本。

 

public T get() {
        //获得当前线程
        Thread t = Thread.currentThread();
        //获得当前线程的ThreadLocalMap实例
        ThreadLocalMap map = getMap(t);
        //如果不为空,说明此线程已经有一个ThreadLocalMap实例
        if (map != null) {
            //map中保存线程所有的本地变量,获得当前本地变量的Entry
            ThreadLocalMap.Entry e = map.getEntry(this);
            //e不为空说明当前本地变量存在,返回对应的value值
            if (e != null)
                return (T)e.value;
        }
        //如果map不存在或map中不存在当前本地变量,返回初始值
        return setInitialValue();
    }

 

//返回线程对象的threadLocals成员变量
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

 

 

private T setInitialValue() {
        //获取初始化值,initialValue是需要覆写的初始化方法
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        //如果map不为空,将初始化值放入到当前线程的ThreadLocalMap对象中
        if (map != null)
            map.set(this, value);
        else
            //当前线程第一次使用本地线程变量,需要对map进行初始化工作
            createMap(t, value);
        //返回初始化值
        return value;
    }

 

public void set(T value) {
        Thread t = Thread.currentThread();
        //获得当前线程的ThreadLocalMap实例
        ThreadLocalMap map = getMap(t);
        if (map != null)
            //将变量的值保存在ThreadLocalMap中,key值为当前线程本地变量
            map.set(this, value);
        else
            createMap(t, value);
    }

 

       代码中对重要的代码逻辑都做了注释。我们看到ThreadLocal的关键就在于ThreadLocalMap这个类。ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取,每个线程分别有一个ThreadLocalMap副本,它只能被当前线程所读取和修改。ThreadLocal类通过操作每一个线程独特的ThreadLocalMap副本,实现了变量访问在不同线程中的隔离。ThreadLocalMap存储的键对中的键值是指向ThreadLocal对象(this),而值就是你所设置的对象了。

 

ThreadLocal的应用

       下面我们通过一个简单的例子来看一下ThreadLocal的使用方式和效果。

 

public class ThreadLocalTest {
	//覆盖ThreadLocal的initialValue()方法设置初始值
	private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {
		public Integer initialValue() {
			return 0;
		}
	};
	//获取下一个序列值
	public int getNextNum() {
		seqNum.set(seqNum.get() + 1);
		return seqNum.get();
	}
	public static void main(String[] args) {	
                ThreadLocalTest sn = new ThreadLocalTest();
		//创建3个线程,共享sn
		TestClient t1 = new TestClient(sn);
		TestClient t2 = new TestClient(sn);
		TestClient t3 = new TestClient(sn);
		t1.start();
		t2.start();
		t3.start();
	}
	private static class TestClient extends Thread {
		private ThreadLocalTest sn;
		public TestClient(ThreadLocalTest sn) {
			this.sn = sn;
		}
		public void run() {
			//每个线程打出3个序列值
			for (int i = 0; i < 3; i++) {
				System.out.println("thread[" + Thread.currentThread().getName() + "] --> sn[" + sn.getNextNum() + "]");
			}
		}
	}
}

 

 

       上面的例子中,创建了三个TestClient线程,共享同一个ThreadLocalTest实例,并产生一组数字序列。运行代码输出如下:

 

thread[Thread-0] --> sn[1]
thread[Thread-1] --> sn[1]
thread[Thread-2] --> sn[1]
thread[Thread-1] --> sn[2]
thread[Thread-0] --> sn[2]
thread[Thread-1] --> sn[3]
thread[Thread-2] --> sn[2]
thread[Thread-0] --> sn[3]
thread[Thread-2] --> sn[3]

 

       从输出结果来看,虽然每个线程所产生的序号共享同一个ThreadLocalTest实例,但它们之间并不会相互干扰,而是各自产生独立的序列号,这是因为我们通过ThreadLocal为每个线程提供了独立的副本。虽然是 static 变量,对于不同的线程并没有共享,而是每个线程各一份,从而也就保证了线程安全。

 

总结

 

       ThreadLocal主要用于解决多线程并发的问题。它为每个线程提供了一个数据的副本,线程通过访问各自的副本,大大减少了线程同步带来的性能损耗,也降低了多线程并发控制的成本。

       Synchonized和ThreadLocal虽然都是用于解决多线程并发访问,但是它们的使用场景有所不同。Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。具体来讲,synchronized利用锁机制使共享资源在某一时该只能被一个线程访问,而ThreadLocal为每个线程都提供了变量的副本,隔离了多线程对数据的数据共享。

       欢迎关注我的公众号一起交流学习

     

参考文章:

http://my.oschina.net/huangyong/blog/159489

http://www.cnblogs.com/dolphin0520/p/3920407.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值