Java-多线程-ThreadLocal

版权声明:欢迎转载,请注明作者和出处 https://blog.csdn.net/baichoufei90/article/details/84072355

Java-多线程-ThreadLocal

0x01 摘要

本文简单分析下ThreadLocal实现原理,再附上小例子。

0x02 ThreadLocal是什么

ThreadLocal提供线程级别的私有局部变量。这些变量和普通变量不同之处在于,通过get或set方法访问这类变量的每个线程都拥有一份独立初始化的变量副本

ThreadLocal通常用private static修饰,可以将状态与该线程建立一对一的关系。

下面这个小例子,当第一次调用ThreadId.get()时会为每个线程生成一个全局唯一的、以1为步长自增的标识符,往后都会保持不变。

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();
     }
 }

在上面的例子中,只要线程存活且该ThreadLocal实例可访问,那么每个线程都会拥有一个隐式引用,指向自己拥有的ThreadLocal变量副本值。

0x03 原理

ThreadLocal原理

3.1 ThreadLocal的属性

3.1.1 HashCode

private final int threadLocalHashCode = nextHashCode();

// 两个连续的hashcode差值
private static final int HASH_INCREMENT = 0x61c88647;

private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
}

private static AtomicInteger nextHashCode = new AtomicInteger();

ThreadLocals依赖于附属于每个线程的线性探测哈希映射(Thread.threadLocals和inheritableThreadLocals,都是ThreadLocalMap类型)
ThreadLocal对象充当键,通过threadLocalHashCode搜索。 这是一个自定义哈希代码(仅在ThreadLocalMaps中有用),它消除了在相同线程使用连续构造的ThreadLocals的常见情况下的冲突,同时在不太常见的情况下保持良好行为。

3.2 ThreadLocal构造方法

 public ThreadLocal() {}

这里什么都没做。

3.3 成员方法

3.3.1 initialValue

protected T initialValue() {
        return null;
}

这个方法会在第一次使用get方法调用时执行,除非在此之前调用了set方法就不会调用initialValue。一般来说该方法只会被调用一次,但如果使用remove清空随后又调用get方法时,又会再次调用initialValue

如果我们想设一个初值,一般就是用匿名内部类的方式重写该方法实现自定义初值,比如:

ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
        @Override
        protected DateFormat initialValue() {
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
} };

3.3.2 get

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();
}

get方法很重要,他被用来获取当前thread私有的threadlocal的变量副本。 如果变量没有当前线程的值,则首先将其初始化为调用initialValue方法的返回值。

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;
}

这个是刚刚get方法里使用的,可以看到他会先调用initialValue方法去拿到初始值,然后获取当前线程的ThreadLocalMap。如果map不存在就以当前threadvalue创建ThreadLocalMap;如果已经存在就以当前ThreadLocal实例为key,value为值放入该ThreadLocalMap。

3.3.3 set

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

跟前面提到的setInitialValue方法差不多,只不过这里指定了value。

3.3.4 remove

 public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
}

获取当前线程的ThreadLocalMap,然后从其中移除当前ThreadLocal实例为key的Entry

3.4 内部类

3.4.1 SuppliedThreadLocal

 static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {

        private final Supplier<? extends T> supplier;

        SuppliedThreadLocal(Supplier<? extends T> supplier) {
            this.supplier = Objects.requireNonNull(supplier);
        }

        @Override
        protected T initialValue() {
            return supplier.get();
        }
}

这个类的主要作用是配合withInitial设定初始值,示例如下:

 private static ThreadLocal<Integer> ti1 = ThreadLocal.withInitial(new Supplier<Integer>() {
        @Override
        public Integer get()
        {
            return 33;
        }
    });
    private static ThreadLocal<Integer> ti2 = ThreadLocal.withInitial(() -> 44);

3.4.2 ThreadLocalMap

ThreadLocalMap是一个自定义的hasmap,他只适合维护threadlocal values,没有向外暴露任何方法。

为了应对长期和高负荷的使用,所以采用了WeakReference来修饰该map的key。也就是说当这些key无其他强引用时,GC会将他们回收。注意,

0x05 坑

5.1 内存泄露

因为每个线程都有一个map,指向使用的ThreadLocal对象,而且这是一个强引用。也就是说,当一个ThreadLocal强引用持续存在时,使用了该ThreadLocal的线程的ThreadLocalMap里的Entry之key虽是弱引用指向该ThreadLocal对象,但是因为还有强引用存在的关系就一直不会被回收,该entry也会随着线程持续存在而存在,造成内存泄露。所以我们应该在每个线程使用完ThreadLocal对象后调用remove方法,手动移除该entry。

5.2 线程池

Thread对象就那么几个,都是复用的。也就是说,他们的ThreadLocalMap是不会变的,就会导致其他Runnable的ThreadLocal值交叉混用,出现问题。

0x06 例子

6.1 DateFormat

SimpleDateFormat并不是线程安全的,所以在阿里的Java开发规范里推荐了用ThreadLocal保证线程安全的做法:

private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
        @Override
        protected DateFormat initialValue() {
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    } };

  String startTimeStr = "2018-01-11 02:17:02.806";
        try {
            Date startTime = df.get().parse(startTimeStr);
            System.out.println("startTime=" + startTime);
            String dfStr = df.get().format(new Date());
            System.out.println("dfStr=" + dfStr);
		}catch (ParseException e) {
            e.printStackTrace();
        }

0xFF 参考文档

JavaDoc-java.lang.ThreadLocal

彻底理解ThreadLocal

猜你喜欢

转载自blog.csdn.net/baichoufei90/article/details/84072355