前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ThreadLocal

ThreadLocal

作者头像
黑洞代码
发布2021-01-14 15:11:29
3040
发布2021-01-14 15:11:29
举报

ThreadLocal源码分析

概述


1.ThreadLocal的介绍

2.ThreadLocal的使用

3.ThreadLocal源码分析

第1节 ThreadLocal介绍


ThreadLocal——线程本地变量副本

当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本

每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。


ThreadLocal类接口很简单,常用方法如下:

1.public void set(T value)设置当前线程的线程局部变量的值。

2.public T get()方法返回当前线程所对应的线程局部变量。

3.public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,

该方法是JDK 5.0新增的方法。当线程结束后,对应该线程的局部变量将自动被垃圾回收,

所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

4.public static <S> ThreadLocal<S> withInitial()方法是JDK1.8新增的额方法。

创建一个线程本地变量副本。默认值由Supplier的get()方法控制。

5.protected T initialValue()方法的访问修饰符是protected,

该方法为第一次调用get方法提供一个初始值。默认情况下,

第一次调用get方法返回值null。在使用时,我们一般会重写ThreadLocal的initialValue方法,

使第一次调用get方法时返回一个我们设定的初始值。

第2节 ThreadLocal的使用


看下面ThreadLocal使用Demo

代码语言:javascript
复制
public class ThreadLocalDemo {
    /**
     * 定义了一个ThreadLocal<Integer>对象,
     * 并重写它的initialValue方法,初始值是3
     * 这个对象会在三个线程间共享
     */
    private ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 3);
    /**
     * 设置一个信号量,许可数为1,让三个线程顺序执行
     */
    private Semaphore semaphore = new Semaphore(1);
    /**
     * 一个随机数
     */
    private Random random = new Random();

    /**
     * 每个线程中调用这个对象的get方法,再调用一个set方法设置一个随机值
     */
    public class Worker implements Runnable {
        @Override
        public void run() {

            try {
                // 随机延时1s以内的时间
                Thread.sleep(random.nextInt(1000));
                // 获取许可
                semaphore.acquire();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 从threadLocal中获取值
            int value = threadLocal.get();
            System.out.println(Thread.currentThread().getName() + " threadLocal old value : " + value);
            // 修改value值
            value = random.nextInt();
            // 新的value值放入threadLocal中
            threadLocal.set(value);
            System.out.println(Thread.currentThread().getName() + " threadLocal new value: " + value);
            System.out.println(Thread.currentThread().getName() + " threadLocal latest value : " + threadLocal.get());
            // 释放信号量
            semaphore.release();
            // 在线程池中,当线程退出之前一定要记得调用remove方法,因为在线程池中的线程对象是循环使用的
            threadLocal.remove();
        }
    }

    /**
     * 创建三个线程,每个线程都会对ThreadLocal对象进行操作
     */
    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(3);
        ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo();
        es.execute(threadLocalDemo.new Worker());
        es.execute(threadLocalDemo.new Worker());
        es.execute(threadLocalDemo.new Worker());
        es.shutdown();
    }
}

执行结果如下。

代码语言:javascript
复制
pool-1-thread-3 threadLocal old value : 3
pool-1-thread-3 threadLocal new value: -189935755
pool-1-thread-3 threadLocal latest value : -189935755
pool-1-thread-2 threadLocal old value : 3
pool-1-thread-2 threadLocal new value: -180783580
pool-1-thread-2 threadLocal latest value : -180783580
pool-1-thread-1 threadLocal old value : 3
pool-1-thread-1 threadLocal new value: -1299325901
pool-1-thread-1 threadLocal latest value : -1299325901

第3节 ThreadLocal源码分析


ThreadLocal内部结构

/*TheadLocal中的set()源码分析*/

代码语言:javascript
复制
/**
 *  设置ThreadLocal变量的值
 */
public void set(T value) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取当前线程的ThreadLocalMap 
    ThreadLocalMap map = getMap(t);
    // 如果当前线程的ThreadLocalMap非空
    if (map != null)
        // 往ThreadLocalMap中添加K-V
        map.set(this, value);
    else
        // 如果当前线程的ThreadLocalMap为空
        // 创建ThreadLocalMap对象
        createMap(t, value);
}

ThreadLocalMap map = getMap(t);是获取当前线程内部的ThreadLocalMap对象。

代码语言:javascript
复制
/**
 * 返回与当前的线程对象相关的ThreadLocalMap对象
 */
ThreadLocalMap getMap(Thread t) {
    // 返回线程t的threadLocals对象
    return t.threadLocals;
}

Thread类的内部有个属性ThreadLocalMap属性。

代码语言:javascript
复制
/* Thread类的属性 */
ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap的getEntry()方法如下。

代码语言:javascript
复制
/**
   * 获取key对应的entry
   */
  private Entry getEntry(ThreadLocal<?> key) {
      // key的hashcode & 1111,即保留key的hashcode的低4位
      int i = key.threadLocalHashCode & (table.length - 1);
      // 获取hash表位置i处的Entry对象
      Entry e = table[i];
      // e非空且e的key等于key,说明就是要查找的Entry对象
      if (e != null && e.get() == key)
          // 返回Entry对象e
          return e;
      else
          // 没有找到对应的Entry,需要继续查找
          return getEntryAfterMiss(key, i, e);
  }

getEntryAfterMiss()方法如下。

代码语言:javascript
复制
/**
   * 直接在hash表对应的槽位上没有找到对饮的Entry
   * 采用线性探测法继续在hash表上查找
   */
  private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
      // hash表的Entry数组
      Entry[] tab = table;
      // Entry数组的长度
      int len = tab.length;
    // 如果e非空
      while (e != null) {
          // 获取e的key
          ThreadLocal<?> k = e.get();
          // 如果k等于key,找到对应的Entry
          if (k == key)
             // 返回Entry对象
              return e;
          // 如果k为空
          if (k == null)
              // 移除这个失效的Entry对象
              expungeStaleEntry(i);
          else
              // 修改i为hash表的下一个位置
              i = nextIndex(i, len);
          // 修改e为tab[i],进入下一次循环
          e = tab[i];
      }
      // 没有找到对应的Entry对象
      return null;
  }

expungeStaleEntry()方法如下。

代码语言:javascript
复制
/**
   * 清理失效的Entry
   */
  private int expungeStaleEntry(int staleSlot) {
      Entry[] tab = table;
      int len = tab.length;

      // 清理staleSlot位置的Entry
      tab[staleSlot].value = null;
      tab[staleSlot] = null;
      size--;

      // 调整hash表直到后面第一个tab[i]为null为止
      Entry e;
      int i;
      for (i = nextIndex(staleSlot, len);
           (e = tab[i]) != null;
           i = nextIndex(i, len)) {
          ThreadLocal<?> k = e.get();
          // 如果key为null,将该entry置为null
          if (k == null) {
              e.value = null;
              tab[i] = null;
              size--;
          } else {
              // 下面是新计算一下hash值,如果位置与当前位置不同
        // 需要重新找一个位置放该节点。用的也是线性探测法
              int h = k.threadLocalHashCode & (len - 1);
              if (h != i) {
                  tab[i] = null;

                  // Unlike Knuth 6.4 Algorithm R, we must scan until
                  // null because multiple entries could have been stale.
                  while (tab[h] != null)
                      h = nextIndex(h, len);
                  tab[h] = e;
              }
          }
      }
      return i;
  }
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-08-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 落叶飞翔的蜗牛 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档