并发编程第三篇——ThreadLocal
源码关于类的注释初识
源码在类的定义之前有一段注释,大体是这个意思:
ThreadLocal类提供 thread-local variabels(线程局部变量)
这些变量与普通变量不同,每个线程都可以通过其 get 或 set方法来访问自己的独立初始化的变量副本
ThreadLocal的实例通常在类中是private static的一个域,这个域维持与线程相关联的某种状态(例如用户ID或事务ID)
注释中给出了这样一个例子:
import java.util.concurrent.atomic.AtomicInteger; public class ThreadId { // Atomic integer containing the next thread ID to be assigned private static final AtomicInteger nextId = new AtomicInteger(0); // Thread local variable containing each thread's ID private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return nextId.getAndIncrement(); } }; // Returns the current thread's unique ID, assigning it if necessary public static int get() { return threadId.get(); } }
ThreadId这个类对于每个线程都生成一个唯一本地标识(线程id值),这个id在第一次被调用后分配,
并在随后的调用中保持不变
关键代码
public class ThreadLocal<T> { /** * 内部类ThreadLocalMap中使用,相当于HashMap中的hashCode设计
* ThradLocalMap类设计了一个静态内部类Entry,其构造方法为(ThreadLocal<?> k,Object v)
* 搜索Entry时,根据k.threadLocalHashCode值做下标寻址计算 */ private final int threadLocalHashCode = nextHashCode(); /** * The next hash code to be given out. Updated atomically. Starts at zero. */ private static AtomicInteger nextHashCode = new AtomicInteger(); /** * hash增长值,设计为7
* */ private static final int HASH_INCREMENT = 0x61c88647; /** * 获取下一个hash值 */ private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } /**
* 设置ThreadLocal初始值,第一次调用get()方法获取初始值的时候将调用此方法
* 正常情况下,该方法最多只会被一个线程调用一次,但是如果后续调用了remove()方法接着调用get()时,
* 该方法也会被调用 * 默认返回是null,如果想设置为非null值,在创建ThreadLocal时使用匿名内部类覆盖此方法*/ protected T initialValue() { return null; } /** * lambda方式初始化,比使用匿名内部类覆盖initialValue()更易理解
* ThreadLocal<String> local = ThreadLocal.withInitial(()->"hello")
*/ public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) { return new SuppliedThreadLocal<>(supplier); } /** * 构造方法 * @see #withInitial(java.util.function.Supplier) */ public ThreadLocal() { } /*** 核心方法,获取当前线程变量的值 * @return the current thread's value of this thread-local */ 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(); }/** * 核心方法,设置本地线程变量的值,支持泛型*/ public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } /** * 清除变量值*/ public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } /** * 核心逻辑之一,查看Thread.java源码可以看到
* Thread类持有一个ThreadLocal.ThreadLocalMap类型的变量threadLocals*/ ThreadLocalMap getMap(Thread t) { return t.threadLocals; } /** * Create the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @param firstValue value for the initial entry of the map */ void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }/**
* ThreadLocal设计的关键,这是一个静态内部类,HashMap结构
* set方法:set(ThreadLocal<?> key,Object value),key是ThreadLocal对象
* 但是搜索下标,使用的是key.threadLocalHashCode做计算 */ static class ThreadLocalMap {
//Entry是ThreadLocalMap的一个静态内部类
private Entry[] table; ………… } }
使用场景
同一个线程内 不同逻辑间需要共享数据(但又无法通过传值来共享数据),或者在同一个线程内为避免重复创建对象 而希望数据重用等情况
注意
对同一个线程的不同ThreadLocal实例来讲,这些ThreadLocal实例共享一个table数组,每个ThreadLocal实例在数组中的索引不同
Thread,ThreadLocal,ThreadLocalMap之间的关系
- 每个Thread都持有一个ThreadLocalMap属性
- ThreadLocalMap里面存储的key是ThreadLocal对象,value是要设置的值
- ThreadLocalMap为ThreadLocal定义的一个内部类
- 一个Thread可以定义多个ThreadLocal实例,通过开放寻址法找到table中的位置,而不是HashMap类似的链地址法
找到一张比较形象的图说明这个结构关系
做了个实验
定义一个任务,预期顺序打印序列号
public class MyRunnable implements Runnable { private ThreadLocalTest test; public MyRunnable(ThreadLocalTest test) { this.test = test; } /** * 让线程顺序获取序列号打印 */ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + " 获取到序列号:" + test.getNextNum()); } } }
使用ThreadLocal前
public class ThreadLocalTest { private static Integer seqNum = 0; public int getNextNum(){ seqNum++; //为了效果这里加个休眠 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return seqNum; } public static void main(String[] args) { ThreadLocalTest test = new ThreadLocalTest(); new Thread(new MyRunnable(test),"线程1").start(); new Thread(new MyRunnable(test),"线程2").start(); new Thread(new MyRunnable(test),"线程3").start(); } }
打印结果:
线程2 获取到序列号:3 线程3 获取到序列号:3 线程1 获取到序列号:3 线程1 获取到序列号:6 线程3 获取到序列号:7 线程2 获取到序列号:8 线程3 获取到序列号:9 线程2 获取到序列号:9 线程1 获取到序列号:9 线程3 获取到序列号:12 线程2 获取到序列号:12 线程1 获取到序列号:12 线程2 获取到序列号:15 线程3 获取到序列号:15 线程1 获取到序列号:15 线程1 获取到序列号:18 线程3 获取到序列号:18 线程2 获取到序列号:18 线程3 获取到序列号:21 线程1 获取到序列号:21 线程2 获取到序列号:21 线程1 获取到序列号:24 线程2 获取到序列号:24 线程3 获取到序列号:24 线程2 获取到序列号:27 线程3 获取到序列号:27 线程1 获取到序列号:27 线程2 获取到序列号:30 线程3 获取到序列号:30 线程1 获取到序列号:30
使用ThreadLocal
public class ThreadLocalTest { /** * 定义匿名子类创建ThreadLocal的变量 */ private static ThreadLocal<Integer> seqNum = ThreadLocal.withInitial(() -> 0); /** * 获取下一个序列号 */ public int getNextNum() { seqNum.set(seqNum.get() + 1); return seqNum.get(); } public static void main(String[] args) { ThreadLocalTest test = new ThreadLocalTest(); new Thread(new MyRunnable(test),"线程1").start(); new Thread(new MyRunnable(test),"线程2").start(); new Thread(new MyRunnable(test),"线程3").start(); } }
打印结果:
线程2 获取到序列号:1 线程1 获取到序列号:1 线程2 获取到序列号:2 线程2 获取到序列号:3 线程1 获取到序列号:2 线程2 获取到序列号:4 线程3 获取到序列号:1 线程2 获取到序列号:5 线程1 获取到序列号:3 线程2 获取到序列号:6 线程3 获取到序列号:2 线程2 获取到序列号:7 线程1 获取到序列号:4 线程2 获取到序列号:8 线程3 获取到序列号:3 线程2 获取到序列号:9 线程1 获取到序列号:5 线程2 获取到序列号:10 线程3 获取到序列号:4 线程3 获取到序列号:5 线程3 获取到序列号:6 线程3 获取到序列号:7 线程3 获取到序列号:8 线程1 获取到序列号:6 线程3 获取到序列号:9 线程1 获取到序列号:7 线程3 获取到序列号:10 线程1 获取到序列号:8 线程1 获取到序列号:9 线程1 获取到序列号:10
可以看到,每个线程自己,获取到的序列号是顺序从1到10的