前面的文章里,咱们学习了有关锁的使用,锁的机制是保证同一时刻只能有一个线程访问临界区的资源,也就是经过控制资源的手段来保证线程安全,这当然是一种有效的手段,但程序的运行效率也所以大大下降。那么,有没有更好的方式呢?答案是有的,既然锁是严格控制资源的方式来保证线程安全,那咱们能够反其道而行之,增长更多资源,保证每一个线程都能获得所需对象,各自为营,互不影响,从而达到线程安全的目的,而ThreadLocal即是采用这样的思路。安全
ThreadLocal翻译成中文的话大概能够说是:线程局部变量,也就是只有当前线程可以访问。它的设计做用是为每个使用该变量的线程都提供一个变量值的副本,每一个线程都是改变本身的副本而且不会和其余线程的副本冲突,这样一来,从线程的角度来看,就好像每一个线程都拥有了该变量。bash
下面是一个简单的实例:ide
public class ThreadLocalDemo {
static ThreadLocal<Integer> local = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 0;
}
};
public static class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0;i<3;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int value = local.get();
System.out.println(Thread.currentThread().getName() + ":" + value);
local.set(value + 1);
}
}
}
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
t1.start();
t2.start();
}
}
复制代码
上面的代码不难理解,首先是定义了一个名为 local
的ThreadLocal变量,并初识变量的值为0,而后是定义了一个实现Runnable接口的内部类,在其run方法中对local
的值作读取和加1的操做,最后是main方法中开启两个线程来运行内部类实例。函数
以上就是代码的大概逻辑,运行main函数后,程序的输出结果以下:学习
Thread-0:0
Thread-1:0
Thread-1:1
Thread-0:1
Thread-1:2
Thread-0:2
复制代码
从结果能够看出,虽然两个线程都共用一个Runnable实例,但两个线程中所展现的ThreadLocal的数据值并不会相互影响,也就是说这种状况下的local
变量保存的数据至关因而线程安全的,只能被当前线程访问。ui
那么ThreadLocal内部是怎么保证对象是线程私有的呢?毫无疑问,答案须要从源码中查找。回顾前面的代码,能够发现其中调用了ThreadLocal的两个方法set 和 get,咱们就从这两个方法入手。this
先看 set() 的源码:spa
public void set(T value) {
Thread t = Thread.currentThread();
// 获取线程的ThreadLocalMap,返回map
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
//map为空,建立
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
复制代码
set的代码逻辑比较简单,主要是把值设置到当前线程的一个ThreadLocalMap对象中,而ThreadLocalMap能够理解成一个Map,它是定义在Thread类中内部的成员,初始化是为null,线程
ThreadLocal.ThreadLocalMap threadLocals = null;
复制代码
不过,与常见的Map实现类,如HashMap之类的不一样的是,ThreadLocalMap中的Entry是继承于WeakReference类的,保持了对 “键” 的弱引用和对 “值” 的强引用,这是类的源码:翻译
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//省略剩下的源码
....................
}
复制代码
从源码中中能够看出,Entry构造函数中的参数 k 就是ThreadLocal实例,调用super(k) 代表对 k 是弱引用,使用弱引用的缘由在于,当没有强引用指向 ThreadLocal 实例时,它可被回收,从而避免内存泄露,那么为什么须要防止内存泄露呢?缘由下面会说到。
接着说set方法的逻辑,当调用set方法时,实际上是将数据写入threadLocals这个Map对象中,这个Map的key为ThreadLocal当前对象,value就是咱们存入的值。而threadLocals自己能保存多个ThreadLocal对象,至关于一个ThreadLocal集合。
接着看 get() 的源码:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//设置初识值到ThreadLocal中并返回
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
复制代码
get方法的逻辑也是比较简单的,就是直接获取当前线程的ThreadLocalMap对象,若是该对象不为空就返回它的value值,不然就把初始值设置到ThreadLocal中并返回。
看到这,咱们大概就能明白为何ThreadLocal能实现线程私有的原理了,其实就是每一个线程都维护着一个ThreadLocal的容器,这个容器就是ThreadLocalMap,能够保存多个ThreadLocal对象。而调用ThreadLocal的set或get方法其实就是对当前线程的ThreadLocal变量操做,与其余线程是分开的,因此才能保证线程私有,也就不存在线程安全的问题了。
然而,该方案虽然能保证线程私有,但却会占用大量的内存,由于每一个线程都维护着一个Map,当访问某个ThreadLocal变量后,线程会在本身的Map内维护该ThreadLocal变量与具体实现的映射,若是这些映射一直存在,就代表ThreadLocal 存在引用的状况,那么系统GC就没法回收这些变量,可能会形成内存泄露。
针对这种状况,上面所说的ThreadLocalMap中Entry的弱引用就起做用了。
最后,总结一下ThreadLocal和同步机制之间的区别吧。
实现机制:
同步机制采用了“以时间换空间”的方式,控制资源保证同一时刻只能有一个线程访问。
ThreadLocal采用了“以空间换时间”的方式,为每个线程都提供一份变量的副本,从而实现同时访问而互不影响,但由于每一个线程都维护着一份副本,对内存空间的占用会增长。
数据共享:
同步机制是对公共资源作控制访问的方式来保证线程安全,但资源还是共享状态,可用于线程间的通讯;
ThreadLocal是每一个线程都有本身的资源(变量)副本,互相之间不影响,也就不存在共享的说法了。