并发编程第三篇——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的

 

 

posted @ 2020-11-15 21:49  鼠标的博客  阅读(227)  评论(0编辑  收藏  举报