1.介绍
深挖过threadLocal之后,一句话概括:Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。所以ThreadLocal的应用场合,最适合的是按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。
早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。所以,在Java中编写线程局部变量的代码相对来说要笨拙一些,因此造成线程局部变量没有在Java开发者中得到很好的普及。
ThreadLocal、ThreadLocal、Thread之间的关系
ThreadLocalMap是ThreadLocal内部类,由ThreadLocal创建,用来存储数据,采用类似hashmap机制,存储了以threadLocal为key,需要隔离的数据为value的Entry键值对数组结构。Thread有ThreadLocal.ThreadLocalMap类型的属性。
ThreadLocal,有个ThreadLocalMap类型的属性,连接ThreadLocalMap和Thread。来处理Thread的TheadLocalMap属性,包括init初始化属性赋值、get对应的变量,set设置变量等。通过当前线程,获取线程上的ThreadLocalMap属性,对数据进行get、set等操作。
源码如下:
Thread的属性:
1 public 2 class Thread implements Runnable { 3 /*...其他属性...*/ 4 5 /* ThreadLocal values pertaining to this thread. This map is maintained 6 * by the ThreadLocal class. */ 7 ThreadLocal.ThreadLocalMap threadLocals = null; 8 9 /* 10 * InheritableThreadLocal values pertaining to this thread. This map is 11 * maintained by the InheritableThreadLocal class. 12 */ 13 ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
ThreadLocal和ThreadLocalMap:
1 public class ThreadLocal<T> { 2 /**..其他属性和方法稍后介绍...*/ 3 /** 4 * ThreadLocalMap is a customized hash map suitable only for 5 * maintaining thread local values. No operations are exported 6 * outside of the ThreadLocal class. The class is package private to 7 * allow declaration of fields in class Thread. To help deal with 8 * very large and long-lived usages, the hash table entries use 9 * WeakReferences for keys. However, since reference queues are not 10 * used, stale entries are guaranteed to be removed only when 11 * the table starts running out of space. 12 */ 13 static class ThreadLocalMap {
由ThreadLocal对Thread的TreadLocalMap进行赋值:
1 /** 2 * Create the map associated with a ThreadLocal. Overridden in 3 * InheritableThreadLocal. 4 * 5 * @param t the current thread 6 * @param firstValue value for the initial entry of the map 7 */ 8 void createMap(Thread t, T firstValue) { 9 t.threadLocals = new ThreadLocalMap(this, firstValue); 10 }
2.使用
ThreadLocal类核心方法set、get、initialValue、withInitial、setInitialValue、remove:
1 /** 2 * Returns the current thread's "initial value" for this 3 * thread-local variable. This method will be invoked the first 4 * time a thread accesses the variable with the {@link #get} 5 * method, unless the thread previously invoked the {@link #set} 6 * method, in which case the {@code initialValue} method will not 7 * be invoked for the thread. Normally, this method is invoked at 8 * most once per thread, but it may be invoked again in case of 9 * subsequent invocations of {@link #remove} followed by {@link #get}. 10 * 11 * <p>This implementation simply returns {@code null}; if the 12 * programmer desires thread-local variables to have an initial 13 * value other than {@code null}, {@code ThreadLocal} must be 14 * subclassed, and this method overridden. Typically, an 15 * anonymous inner class will be used. 16 * 17 * @return the initial value for this thread-local 18 */ 19 protected T initialValue() { 20 return null; 21 } 22 23 /** 24 * Creates a thread local variable. The initial value of the variable is 25 * determined by invoking the {@code get} method on the {@code Supplier}. 26 * 27 * @param <S> the type of the thread local's value 28 * @param supplier the supplier to be used to determine the initial value 29 * @return a new thread local variable 30 * @throws NullPointerException if the specified supplier is null 31 * @since 1.8 32 */ 33 public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) { 34 return new SuppliedThreadLocal<>(supplier); 35 } 36 37 /** 38 * Creates a thread local variable. 39 * @see #withInitial(java.util.function.Supplier) 40 */ 41 public ThreadLocal() { 42 } 43 44 /** 45 * Returns the value in the current thread's copy of this 46 * thread-local variable. If the variable has no value for the 47 * current thread, it is first initialized to the value returned 48 * by an invocation of the {@link #initialValue} method. 49 * 50 * @return the current thread's value of this thread-local 51 */ 52 public T get() { 53 Thread t = Thread.currentThread(); 54 ThreadLocalMap map = getMap(t); 55 if (map != null) { 56 ThreadLocalMap.Entry e = map.getEntry(this); 57 if (e != null) { 58 @SuppressWarnings("unchecked") 59 T result = (T)e.value; 60 return result; 61 } 62 } 63 return setInitialValue(); 64 } 65 66 /** 67 * Variant of set() to establish initialValue. Used instead 68 * of set() in case user has overridden the set() method. 69 * 70 * @return the initial value 71 */ 72 private T setInitialValue() { 73 T value = initialValue(); 74 Thread t = Thread.currentThread(); 75 ThreadLocalMap map = getMap(t); 76 if (map != null) 77 map.set(this, value); 78 else 79 createMap(t, value); 80 return value; 81 } 82 83 /** 84 * Sets the current thread's copy of this thread-local variable 85 * to the specified value. Most subclasses will have no need to 86 * override this method, relying solely on the {@link #initialValue} 87 * method to set the values of thread-locals. 88 * 89 * @param value the value to be stored in the current thread's copy of 90 * this thread-local. 91 */ 92 public void set(T value) { 93 Thread t = Thread.currentThread(); 94 ThreadLocalMap map = getMap(t); 95 if (map != null) 96 map.set(this, value); 97 else 98 createMap(t, value); 99 } 100 101 /** 102 * Removes the current thread's value for this thread-local 103 * variable. If this thread-local variable is subsequently 104 * {@linkplain #get read} by the current thread, its value will be 105 * reinitialized by invoking its {@link #initialValue} method, 106 * unless its value is {@linkplain #set set} by the current thread 107 * in the interim. This may result in multiple invocations of the 108 * {@code initialValue} method in the current thread. 109 * 110 * @since 1.5 111 */ 112 public void remove() { 113 ThreadLocalMap m = getMap(Thread.currentThread()); 114 if (m != null) 115 m.remove(this); 116 }
- initialValue返回该线程局部变量的初始值。该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
- withInitial提供一个Supplier的lamda表达式用来当做初始值,java8引入。
- setInitialValue设置初始值。在get操作没有对应的值时,调用此方法。private方法,防止被覆盖。过程和set类似,只不过是用initialValue作为value进行设置。
- set设置当前线程对应的线程局部变量的值。先取出当前线程对应的threadLocalMap,如果不存在则用创建一个,否则将value放入以this,即threadLocal为key的映射的map中,其实threadLocalMap内部和hashMap机制一样,存储了Entry键值对数组,后续会深挖threadLocalMap。
- get该方法返回当前线程所对应的线程局部变量。和set类似,也是先取出当前线程对应的threadLocalMap,如果不存在则用创建一个,但是是用inittialValue作为value放入到map中,且返回initialValue,否则就直接从map取出this即threadLocal对应的value返回。
- remove将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。需要注意的是,如果remove之后又调用了get,会重新初始化一次,即再次调用initialValue方法,除非在get之前调用set设置过值。
3.案例
本案例在多线程中对ThreadLocal和线程局部变量进行测试
1.首先,测试一下线程全局变量在多个线程下的使用,代码如下:
1 import java.util.Random; 2 3 public class testThreadLocal extends Thread{ 6 7 private static boolean flag; 8 9 public void run() 10 { 11 Random random=new Random(); 12 int a=random.nextInt(20); 13 flag=flag(a); 14 System.out.println("其他线程"+a+"启动,flag="+flag); 15 } 16 17 public static void main(String[] args) { 18 flag=flag(3); 19 System.out.println("主线程启动,flag="+flag); 20 21 for (int i = 0; i < 10; i++) { 22 new testThreadLocal().start(); 23 } 24 } 25 26 public static boolean flag(int a){ 27 if(a>4){ 28 flag=true; 29 return flag; 30 }else { 31 return flag; 32 } 33 } 34 } 35
结果如下:
明显有问题,所以在多线程环境下,是不能使用线程全局变量的。
2.线程局部变量测试,代码如下:
1 import java.util.Random; 2 3 public class testThreadLocal extends Thread{ 4 public void run() 5 { 6 Random random=new Random(); 7 int a=random.nextInt(20); 8 boolean flag=flag(a,false); 9 System.out.println("其他线程"+a+"启动,flag="+flag); 10 } 11 public static void main(String[] args) { 12 13 boolean flag=flag(3,false); 14 System.out.println("主线程启动,flag="+flag); 15 16 for (int i = 0; i < 10; i++) { 17 new testThreadLocal().start(); 18 } 19 20 } 21 22 public static boolean flag(int a,boolean flag){ 23 if(a>4){ 24 flag=true; 25 return flag; 26 }else { 27 return flag; 28 } 29 } 30 }
运行结果如下:
结果正常!
3.ThreadLocal测试
1 import java.util.Random; 2 3 public class testThreadLocal extends Thread{ 4 5 private static ThreadLocal<Boolean> tlflag=new ThreadLocal<>(); 6 7 public void run() 8 { 9 Random random=new Random(); 10 int a=random.nextInt(20); 11 flag(a); 12 System.out.println("ThreadLocal形式,其他线程"+a+"启动,flag="+tlflag.get()); 13 } 14 15 public static void main(String[] args) { 16 17 flag(3); 18 System.out.println("ThreadLocal形式,主线程启动,flag="+tlflag.get()); 19 20 for (int i = 0; i < 10; i++) { 21 new testThreadLocal().start(); 22 } 23 } 24 25 public static void flag(int a){ 26 27 if(a>4){ 28 tlflag.set(true); 29 }else { 30 tlflag.set(false); 31 } 32 } 33 }
运行结果为:
结果正常!
部分内容参考自博客:https://blog.csdn.net/lufeng20/article/details/24314381