前言
为柯城等地区用户提供了全套网页设计制作服务,及柯城网站建设行业解决方案。主营业务为网站制作、成都做网站、柯城网站设计,以传统方式定制建设网站,并提供域名空间备案等一条龙服务,秉承以专业、用心的态度为用户提供真诚的服务。我们深信只要达到每一位用户的要求,就会得到认可,从而选择与我们长期合作。这样,我们也可以走得更远!
多线程的线程安全问题是微妙而且出乎意料的,因为在没有进行适当同步的情况下多线程中各个操作的顺序是不可预期的,多线程访问同一个共享变量特别容易出现并发问题,特别是多个线程需要对一个共享变量进行写入时候,为了保证线程安全,
一般需要使用者在访问共享变量的时候进行适当的同步,如下图所示:
可以看到同步的措施一般是加锁,这就需要使用者对锁也要有一定了解,这显然加重了使用者的负担。那么有没有一种方式当创建一个变量的时候,每个线程对其进行访问的时候访问的是自己线程的变量呢?其实ThreaLocal就可以做这个事情,注意一下,ThreadLocal的出现并不是为了解决上面的问题而出现的。
ThreadLocal是在JDK包里面提供的,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的本地拷贝,多个线程操作这个变量的时候,实际是操作自己本地内存里面的变量,从而避免了线程安全问题,创建一个ThreadLocal变量后,
每个线程会拷贝一个变量到自己的本地内存,如下图:
好了,现在我们思考一个问题:ThreadLocal的实现原理,ThreadLocal作为变量的线程隔离方式,其内部又是如何实现的呢?
首先我们要看ThreadLocal的类图结构,如下图所示:
如
上类图可见,Thread类中有一个threadLocals和inheritableThreadLocals 都是ThreadLocalMap类型的变量,而ThreadLocalMap是一个定制化的Hashmap,默认每个线程中这两个变量都为null,只有当线程第一次调用了ThreadLocal的set或者get方法的时候才会创建。
其实每个线程的本地变量不是存到ThreadLocal实例里面的,而是存放到调用线程的threadLocals变量里面。也就是说ThreadLocal类型的本地变量是存放到具体线程内存空间的。
ThreadLocal其实就是一个外壳,它通过set方法把value值放入调用线程threadLocals里面存放起来,当调用线程调用它的get方法的时候再从当前线程的threadLocals变量里面拿出来使用。如果调用线程如果一直不终止的话,那么这个本地变量会一直存放到调用线程的threadLocals变量里面,
因此,当不需要使用本地变量时候可以通过调用ThreadLocal变量的remove方法,从当前线程的threadLocals变量里面删除该本地变量。可能还有人会问threadLocals为什么设计为Map结构呢?很明显是因为每个线程里面可以关联多个ThreadLocal变量。
接下来我们可以进入到ThreadLocal中的源码如看看,如下代码所示:
主要看set,get,remove这三个方法的实现逻辑,如下:
先看set(T var1)方法
public void set(T var1) { //(1)获取当前线程 Thread var2 = Thread.currentThread(); //(2) 当前线程作为key,去查找对应的线程变量,找到则设置 ThreadLocal.ThreadLocalMap var3 = this.getMap(var2); if(var3 != null) { var3.set(this, var1); } else { //(3) 第一次调用则创建当前线程对应的Hashmap this.createMap(var2, var1); } }
如上代码(1)首先获取调用线程,然后使用当前线程作为参数调用了 getMap(var2) 方法,getMap(Thread var2) 代码如下:
ThreadLocal.ThreadLocalMap getMap(Thread var1) { return var1.threadLocals; }
可知getMap(var2) 所作的就是获取线程自己的变量threadLocals,threadlocal变量是绑定到了线程的成员变量里面。
如果getMap(var2) 返回不为空,则把 value 值设置进入到 threadLocals,也就是把当前变量值放入了当前线程的内存变量 threadLocals,threadLocals 是个 HashMap 结构,其中 key 就是当前 ThreadLocal 的实例对象引用,value 是通过 set 方法传递的值。
如果 getMap(var2) 返回空那说明是第一次调用 set 方法,则创建当前线程的 threadLocals 变量,下面看 createMap(var2, var1) 里面做了啥呢?
void createMap(Thread var1, T var2) { var1.threadLocals = new ThreadLocal.ThreadLocalMap(this, var2); }
可以看到的就是创建当前线程的threadLocals变量。
接下来我们再看get()方法,代码如下:
public T get() { //(4)获取当前线程 Thread var1 = Thread.currentThread(); //(5)获取当前线程的threadLocals变量 ThreadLocal.ThreadLocalMap var2 = this.getMap(var1); //(6)如果threadLocals不为null,则返回对应本地变量值 if(var2 != null) { ThreadLocal.ThreadLocalMap.Entry var3 = var2.getEntry(this); if(var3 != null) { Object var4 = var3.value; return var4; } } //(7)threadLocals为空则初始化当前线程的threadLocals成员变量。 return this.setInitialValue(); }
代码(4)首先获取当前线程实例,如果当前线程的threadLocals变量不为null则直接返回当前线程的本地变量。否则执行代码(7)进行初始化,setInitialValue()的代码如下:
private T setInitialValue() { //(8)初始化为null Object var1 = this.initialValue(); Thread var2 = Thread.currentThread(); ThreadLocal.ThreadLocalMap var3 = this.getMap(var2); //(9)如果当前线程变量的threadLocals变量不为空 if(var3 != null) { var3.set(this, var1); //(10)如果当前线程的threadLocals变量为空 } else { this.createMap(var2, var1); } return var1; }
如上代码如果当前线程的 threadLocals 变量不为空,则设置当前线程的本地变量值为 null,否者调用 createMap 创建当前线程的 createMap 变量。
接着我们在看看void remove()方法,代码如下:
public void remove() { ThreadLocal.ThreadLocalMap var1 = this.getMap(Thread.currentThread()); if(var1 != null) { var1.remove(this); } }
如上代码,如果当前线程的 threadLocals 变量不为空,则删除当前线程中指定 ThreadLocal 实例的本地变量。
接下来我们看看具体演示demo,代码如下:
/** * Created by cong on 2018/6/3. */ public class ThreadLocalTest { //(1)打印函数 static void print(String str) { //1.1 打印当前线程本地内存中localVariable变量的值 System.out.println(str + ":" + localVariable.get()); //1.2 清除当前线程本地内存中localVariable变量 //localVariable.remove(); } //(2) 创建ThreadLocal变量 static ThreadLocallocalVariable = new ThreadLocal<>(); public static void main(String[] args) { //(3) 创建线程one Thread threadOne = new Thread(new Runnable() { public void run() { //3.1 设置线程one中本地变量localVariable的值 localVariable.set("线程1的本地变量"); //3.2 调用打印函数 print("线程1----->"); //3.3打印本地变量值 System.out.println("移除线程1本地变量后的结果" + ":" + localVariable.get()); } }); //(4) 创建线程two Thread threadTwo = new Thread(new Runnable() { public void run() { //4.1 设置线程one中本地变量localVariable的值 localVariable.set("线程2的本地变量"); //4.2 调用打印函数 print("线程2----->"); //4.3打印本地变量值 System.out.println("移除线程2本地变量后的结果" + ":" + localVariable.get()); } }); //(5)启动线程 threadOne.start(); threadTwo.start(); } }
代码(2)创建了一个 ThreadLocal 变量;
代码(3)、(4)分别创建了线程 1和 2;
代码(5)启动了两个线程;
线程 1 中代码 3.1 通过 set 方法设置了 localVariable 的值,这个设置的其实是线程 1 本地内存中的一个拷贝,这个拷贝线程 2 是访问不了的。然后代码 3.2 调用了 print 函数,代码 1.1 通过 get 函数获取了当前线程(线程 1)本地内存中 localVariable 的值;
线程 2 执行类似线程 1。
运行结果如下:
这里要注意一下ThreadLocal的内存泄漏问题
每个线程内部都有一个名字为 threadLocals 的成员变量,该变量类型为 HashMap,其中 key 为我们定义的 ThreadLocal 变量的 this 引用,value 则为我们 set 时候的值,每个线程的本地变量是存到线程自己的内存变量 threadLocals 里面的,如果当前线程一直不消失那么这些本地变量会一直存到,
所以可能会造成内存泄露,所以使用完毕后要记得调用 ThreadLocal 的 remove 方法删除对应线程的 threadLocals 中的本地变量。
解开代码1.2的注释后,再次运行,运行结果如下:
我们有没有想过这样的一个问题:子线程中是否获取到父线程中设置的 ThreadLocal 变量的值呢?
这里可以告诉大家,在子线程中是获取不到父线程中设置的 ThreadLocal 变量的值的。那么有办法让子线程访问到父线程中的值吗?为了解决该问题 InheritableThreadLocal 应运而生,InheritableThreadLocal 继承自 ThreadLocal,提供了一个特性,就是子线程可以访问到父线程中设置的本地变量。
首先我们先进入InheritableThreadLocal这个类的源码去看,如下:
public class InheritableThreadLocalextends ThreadLocal { public InheritableThreadLocal() { } //(1) protected T childValue(T var1) { return var1; } //(2) ThreadLocalMap getMap(Thread var1) { return var1.inheritableThreadLocals; } //(3) void createMap(Thread var1, T var2) { var1.inheritableThreadLocals = new ThreadLocalMap(this, var2); } }
可以看到InheritableThreadlocal继承ThreadLocal,并重写了三个方法,在上面的代码已经标出了。代码(3)可知InheritableThreadLocal重写createMap方法,那么可以知道现在当第一次调用set方法时候创建的是当前线程的inhertableThreadLocals变量的实例,而不再是threadLocals。
代码(2)可以知道当调用get方法获取当前线程的内部map变量时候,获取的是inheritableThreadLocals,而不再是threadLocals。
关键地方来了,重写的代码(1)是何时被执行的,以及如何实现子线程可以访问父线程本地变量的。这个要从Thread创建的代码看起,Thread的默认构造函数以及Thread.java类的构造函数如下:
/** * Created by cong on 2018/6/3. */ public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); } private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) { //... //(4)获取当前线程 Thread parent = currentThread(); //... //(5)如果父线程的inheritableThreadLocals变量不为null if (parent.inheritableThreadLocals != null) //(6)设置子线程中的inheritableThreadLocals变量 this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); this.stackSize = stackSize; tid = nextThreadID(); }
创建线程时候在构造函数里面会调用init方法,前面讲到了inheritableThreadLocal类get,set方法操作的是变量inheritableThreadLocals,所以这里inheritableThreadLocal变量就不为null,所以会执行代码(6),下面看createInheritedMap方法源码,如下:
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); }
可以看到createInheritedMap内部使用父线程的inheritableThreadLocals变量作为构造函数创建了一个新的ThreadLocalMap变量,然后赋值给了子线程的inheritableThreadLocals变量,那么接着进入到ThreadLocalMap的构造函数里面做了什么,源码如下:
private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len]; for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked") ThreadLocal