ThreadLocal及InheritableThreadLocal的原理剖析

咱们知道,线程的不安全问题,主要是因为多线程并发读取一个变量而引发的,那么有没有一种办法可让一个变量是线程独有的呢,这样不就能够解决线程安全问题了么。其实JDK已经为咱们提供了ThreadLocal这个东西。java


ThreadLocal基本使用
git

当使用ThreadLocal维护变量时,ThreadLocal为每一个使用该变量的线程提供独立的变量副本,因此每个线程均可以独立地改变本身的副本,而不会影响其它线程所对应的副本。
ThreadLocal 的主要方法有这么几个:github


initialValue 初始化
set 赋值
get 取值
remove 清空复制代码

下面来看一个简单的使用代码示例:安全

复制代码
public class ThreadLocalDemo {
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() { 
       @Override       
 public Integer initialValue() { 
           return 0;  
      } 
   };    
static class ThreadDemo implements Runnable {   
     @Override   
     public void run() {   
         for (int i = 0; i < 1000; i++) {     
           threadLocal.set(threadLocal.get() + 1);   
         }          
  System.out.println("thread :" + Thread.currentThread().getId() + " is" + threadLocal.get());      
  } 
   } 
   public static void main(String[] args) {  
      for (int i = 0; i < 10; i++) {   
         new Thread(new ThreadDemo()).start();  
      } 
   }
}复制代码

上方代码使用了10个线程循环对一个threadLocal的值进行一千次的加法,若是咱们不知道ThreadLocal的原理的话咱们可能会以为最后打印的值必定是1000、2000、3000。。10000或者是线程不安全的值。
可是若是你执行这段代码你会发现最后打印的都是1000。bash


ThreadLocal原理剖析
多线程

如今咱们来看一下ThreadLocal是如何实现为每一个线程单独维护一个变量的呢。
先来看一下初始化方法。并发

复制代码
protected T initialValue() {      
  return null;
    }复制代码

initialValue 默认是返回空的,因此为了不空指针问题重写了这个方法设置了默认返回值为0,可是呢,虽然这个方法好像是设置默认值的,可是尚未生效,具体请接着往下看。ide

复制代码
public void set(T value) {    
    Thread t = Thread.currentThread();     
   ThreadLocalMap map = getMap(t);    
    if (map != null)        
    map.set(this, value);   
     else          
  createMap(t, value);  
  }复制代码

咱们能够看到set方法首先会获取当前线程,而后经过一个getMap方法获取了ThreadLocalMap,接着来看一下这个map是怎么来的呢。ui

123复制代码
ThreadLocalMap getMap(Thread t) {  
     return t.threadLocals;  
 }复制代码

这个map是在Thread类维护的一个map,下方是Thread类维护的此变量。默认这个map是空的。this

1复制代码
ThreadLocal.ThreadLocalMap threadLocals = null;复制代码

接着往下看代码,若是获取的时候map不为空,则经过set方法把Thread类的threadLocals变量更新。若是是第一次建立的时候则初始化Thread的threadLocals变量。
下方是createMap的代码:

123复制代码
void createMap(Thread t, T firstValue) {    
    t.threadLocals = new ThreadLocalMap(this, firstValue); 
   }复制代码

接下来看个get方法就比较容易理解了。

12345678910111213复制代码
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;        
    }     
   }     
   return setInitialValue();  
  }复制代码

注意关注最后的一个return,看到调用的这个方法名咱们就能够发现这个ThreadLocal的初始化原来是当第一调用get方法时若是尚未被set的时候才会去获取initialValue 方法的返回值。

12345678910复制代码
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;    
}复制代码


使用ThreadLocal最应该注意的事项

首先来看一下线程退出的办法:

123456789101112复制代码
private void exit() {   
     if (group != null) {    
        group.threadTerminated(this);   
        group = null;  
      }     
   target = null;    
    threadLocals = null; 
       inheritableThreadLocals = null;   
     inheritedAccessControlContext = null;  
      blocker = null;     
   uncaughtExceptionHandler = null;  
  }复制代码

咱们看到当线程结束的时候上方第7行会把ThreadLocal的值制为空,这个东西自己是没问题的。可是,若是你是使用的线程池,这个问题可就大了!!!
要知道线程池里的线程执行完一个任务以后紧接着下一个,这中间线程可不会结束,下一个任务得到Thread的值但是上一个任务的遗留数据。
下面是这个问题的示例代码:

12345678910111213141516171819202122复制代码
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {    
    @Override  
      public Integer initialValue() {  
          return 0;   
     }  
  };   
 static class ThreadDemo implements Runnable {    
    @Override       
 public void run() {         
   for (int i = 0; i < 1000; i++) {      
          threadLocal.set(threadLocal.get() + 1);         
   }         
   System.out.println("thread :" + Thread.currentThread().getId() + " is" + threadLocal.get());       
     //threadLocal.remove();    
    }   
 }   
 public static void main(String[] args) {      
  ExecutorService executorService= Executors.newFixedThreadPool(5);   
     for (int i = 0; i < 10; i++) {       
     executorService.submit(new Thread(new ThreadDemo()));       
 } 
   }复制代码

执行这段代码你就会发现一样的操做在线程池里已经得不到同样的结果了。想要解决这种问题也很简单,只须要把ThreadLocal的值在线程执行完清空就能够了。把第14行注释的代码放开再执行如下你就明白了。


InheritableThreadLocal

其实ThreadLocal还有一个比较强大的子类InheritableThreadLocal,它呢能够把父线程生成的变量传递给子线程。
下面来看一下代码示例:

123456789101112131415161718复制代码
public class InheritableThreadLocalDemo {    private static  InheritableThreadLocal<Integer> inheritableThreadLocal = new  InheritableThreadLocal<Integer>();    static class ThreadDemo implements Runnable {        @Override        public void run() {            for (int i = 0; i < 1000; i++) {                inheritableThreadLocal.set(inheritableThreadLocal.get() + 1);            }            System.out.println("thread :" + Thread.currentThread().getId() + " is" + inheritableThreadLocal.get());        }    }    public static void main(String[] args) {        inheritableThreadLocal.set(24);        for (int i = 0; i < 10; i++) {            new Thread(new ThreadDemo()).start();        }    }}复制代码

执行代码会发现程序输出全是1024,这就是由于InheritableThreadLocal吧在主线程设置的值24传递到了那10个子线程中。


InheritableThreadLocal原理剖析

接下来咱们来看一下InheritableThreadLocal为何能够实现这种功能呢。
InheritableThreadLocal是ThreadLocal的子类,
与ThreadLocal相同的set方法

12345678复制代码
public void set(T value) {       Thread t = Thread.currentThread();       ThreadLocalMap map = getMap(t);       if (map != null)           map.set(this, value);       else           createMap(t, value);   }复制代码

不一样点是InheritableThreadLocal重写了createMap方法,将值赋值给了线程的inheritableThreadLocals变量。

123复制代码
void createMap(Thread t, T firstValue) {        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);    }复制代码

再跟进去Thread类的源码看inheritableThreadLocals变量你会发现:我去,这不是跟Threadlocal同样么,一样初始值为null,线程退出的时候清空。没错,就是这样的。也就是说它其实也是一个线程私有的变量,ThreadLocal的功能它是都有的。

那么它又是怎么把父线程的变量传递到子线程的呢?
接着看Thread的构造方法

123复制代码
public Thread() {    init(null, null, "Thread-" + nextThreadNum(), 0);}复制代码

一路追踪init方法你会看见这段代码:

12345678910111213141516171819202122232425262728293031323334353637383940复制代码
private void init(ThreadGroup g, Runnable target, String name,                      long stackSize, AccessControlContext acc) {        if (name == null) {            throw new NullPointerException("name cannot be null");        }        this.name = name;        Thread parent = currentThread();        SecurityManager security = System.getSecurityManager();        if (g == null) {            if (security != null) {                g = security.getThreadGroup();            }            if (g == null) {                g = parent.getThreadGroup();            }        }        g.checkAccess();        if (security != null) {            if (isCCLOverridden(getClass())) {                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);            }        }        g.addUnstarted();        this.group = g;        this.daemon = parent.isDaemon();        this.priority = parent.getPriority();        if (security == null || isCCLOverridden(parent.getClass()))            this.contextClassLoader = parent.getContextClassLoader();        else            this.contextClassLoader = parent.contextClassLoader;        this.inheritedAccessControlContext =                acc != null ? acc : AccessController.getContext();        this.target = target;        setPriority(priority);        if (parent.inheritableThreadLocals != null)            this.inheritableThreadLocals =                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);        this.stackSize = stackSize;        tid = nextThreadID();    }复制代码

仔细观察倒数第5行到倒数第二行你就明白了。

本文全部源码github.com/shiyujun/sy…

相关文章
相关标签/搜索