当前位置:   article > 正文

ThreadLocal是怎么实现线程隔离的_自定义的threadlocal实现的线程隔离工具

自定义的threadlocal实现的线程隔离工具

欢迎关注我的个人微信公众号,公众号中每天发布最新的技术博文

串一串爪娃子

ThreadLocal大家应该都不陌生,见过最多的使用场景应该是和SimpleDateFormat一起使用吧,因为这个SDF非线程安全的,所以需要使用ThreadLocal将它在线程之间隔离开,避免造成脏数据的????。那么ThreadLocal是怎么保证线程安全,又是如何操作的呢?

案例

  1. public static void main(String[] args) {
  2. ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
  3. new Thread(new Runnable() {
  4. @Override
  5. public void run() {
  6. threadLocal.set(1);
  7. threadLocal.set(2);
  8. System.out.println("cc1: " + threadLocal.get());
  9. }
  10. }, "cc1").start();
  11. new Thread(new Runnable() {
  12. @Override
  13. public void run() {
  14. System.out.println("cc2: " + threadLocal.get());
  15. }
  16. }, "cc2").start();
  17. }

输出:

  1. cc1: 2
  2. cc2: null

哦哟~cc2打印出来null,也就是在cc1线程中设置的值在线程cc2中获取不到,这也就是所谓的线程隔离,我们来看下ThreadLocal具体的代码实现吧:

ThreadLocal的set(T t)方法源码

  1. public void set(T value) {
  2. // 获取当前线程
  3. Thread t = Thread.currentThread();
  4. // 获取当前线程的threadLocals属性,这个属性在Thread类中定义的,为Thread的实例变量
  5. ThreadLocalMap map = getMap(t);
  6. // 若线程的ThreadLocalMap已经存在,则调用ThreadLocalMap的set(ThreadLocal<T> key, Object value)方法
  7. // 否则创建新的ThreadLocalMap实例,并set对应的value
  8. if (map != null)
  9. map.set(this, value);
  10. else
  11. createMap(t, value);
  12. }

ThreadLocalMap的set(ThreadLocal key, Object value)方法源码

  1. private void set(ThreadLocal<?> key, Object value) {
  2. Entry[] tab = table;
  3. int len = tab.length;
  4. // 简单计算key所在的位置
  5. int i = key.threadLocalHashCode & (len-1);
  6. // 从key所在位置开始遍历table数组,找到具体key所在的位置
  7. for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
  8. // 获取Entry实例的key值,这里调用的是超类java.lang.ref.Reference中的get(T t)方法
  9. ThreadLocal<?> k = e.get();
  10. // 若k与传入的参数key是同一个,则用参数value替换Entry实例的value,然后结束方法
  11. if (k == key) {
  12. e.value = value;
  13. return;
  14. }
  15. // 若获取的k为null,则表示这个变量已经被删除了,则去清理一下table数组,并对数组中元素进行清理并设置新的Entry实例
  16. if (k == null) {
  17. replaceStaleEntry(key, value, i);
  18. return;
  19. }
  20. }
  21. // 代码走到这一步,说明该线程第一次设置数据,创建新的Entry实例放在table的第i个位置上
  22. tab[i] = new Entry(key, value);
  23. int sz = ++size;
  24. // 清理table中的元素,若长度达到了扩容阈值,则对table进行扩容,扩容为原数组长度的2倍
  25. if (!cleanSomeSlots(i, sz) && sz >= threshold)
  26. rehash();
  27. }

ThreadLocal的createMap(Thread t, T firstValue)方法源码

  1. void createMap(Thread t, T firstValue) {
  2. // 创建一个ThreadLocalMap实例,并赋值给当前线程的实例变量threadLocals
  3. // 这里就是线程隔离的关键所在,每一个线程中的数据都是由线程独有的threadLocals变量存储的
  4. t.threadLocals = new ThreadLocalMap(this, firstValue);
  5. }

ThreadLocalMap的构造器源码

  1. ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
  2. // 实例化Entry数组,长度为初始长度16
  3. table = new Entry[INITIAL_CAPACITY];
  4. // 计算key在数组中的位置
  5. int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
  6. // 创建Entry实例,并放在table的i下标位置
  7. table[i] = new Entry(firstKey, firstValue);
  8. // 实际长度设置为1
  9. size = 1;
  10. // 设置数组扩容阈值(len * 2 / 3)
  11. setThreshold(INITIAL_CAPACITY);
  12. }

以上便是ThreadLocal达到线程隔离的基本解析,讲解的比较基础,其实就是JDK源码鉴赏,还有什么不懂的地方就自己去看源码吧。

延伸下

ThreadLocal的get()方法源码

  1. public T get() {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);
  4. if (map != null) {
  5. ThreadLocalMap.Entry e = map.getEntry(this);
  6. if (e != null) {
  7. @SuppressWarnings("unchecked")
  8. T result = (T)e.value;
  9. return result;
  10. }
  11. }
  12. return setInitialValue();
  13. }

这段代码比较简单,这里就不在进行解释了,我们着重看一下最后一句setInitialValue()这个方法

  1. private T setInitialValue() {
  2. T value = initialValue();
  3. Thread t = Thread.currentThread();
  4. ThreadLocalMap map = getMap(t);
  5. if (map != null)
  6. map.set(this, value);
  7. else
  8. createMap(t, value);
  9. return value;
  10. }
  11. protected T initialValue() {
  12. return null;
  13. }

会发现和set方法类似,只不过是将一个null当做value而已,所以我们在没给ThreadLocal设置值的情况下调用get方法,则会为其创建一个默认的null值并返回null。

留一个思考题

因为我们每个线程的ThreadLocal的key的hash值都是固定的,那么Thread的threadLocals变量的table中会有多少个非null元素呢?

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
  

闽ICP备14008679号