ThreadLocal看完你就明白了(一)

ThreadLocal看完你就明白了(一)

大家好,我是栗子为。

又有好长时间没和大家见面了,这不,小为刚入职嘛,有很多要学要了解的东西,对于即将到来的任务,小为是既期待又担心,期待的是终于能把知识用在工作中了,担心的是如果让你写个并发的场景来解决某个问题,我真的能做到吗?

基于这些担心,小为决定今天和大家一起来看看在并发编程中常用的一个类,ThreadLocal,看看它是如何解决并发场景问题的,这也是面试中常考的,只要谈到高并发,就会问到ThreadLocal,今天就一起和大家拿下它,安排

在开始今天的内容前,小为整理了一下关于ThreadLocal比较经典的几个面试题,大家也可以试着先回答一下,回答不出来不要紧,看完今天的内容,这些问题就会如鱼得水

问题一:ThreadLocal中ThreadLocalMap的数据结构是什么?

问题二:ThreadLocal的key是什么引用,为什么?

问题三:ThreadLocal会有内存泄露的问题吗?

问题四:看过ThreadLocal源码吗?为什么最后会有一个remove方法

好了好了,大家回答得怎么样,别着急,下面都会有答案…



01


什么是ThreadLocal


本地线程?局部线程?

可以这么理解,ThreadLocal提供线程的局部变量。这些变量与正常的变量不同,对于每个线程在访问ThreadLocal实例的时候都会有自己独立的变量副本。实现了线程隔离,避免了线程安全问题



02


ThreadLocal实例



假设有一个资源类,它还有一个属性num,当有多个线程要对它进行增加操作时,我们通常采用的方式是加Synchronized锁,如下

// 资源类
public class ThreadLocalDemo {
  int num = 0;

  // 采用加锁的方式
  public synchronized void addNum() {
    ++num;
  }
}

这样存在一个问题,所有线程都是操作主内存中的变量,如果我想单独统计每个线程对它的操作,该如何做呢,这就需要用到ThreadLocal,给每个线程创建自己的局部变量,这样就可以统计每个线程操作的次数,那么我们如何为线程创建自己的局部变量呢,ThreadLocal给出了两种方式

// 方式一
ThreadLocal<Integer> numLocal1 = new ThreadLocal<Integer>() {
  @Override
  protected Integer initialValue() {
    return 0;
  }
};

// 方式二,推荐使用,匿名内部类
ThreadLocal<Integer> numLocal2 = ThreadLocal.withInitial(() -> 0);

这两种方式都表明创建了一个线程局部变量,并赋予了初始值0,推荐使用第二种方式

这只是创建了局部变量,那如何定义操作,使得每次让numLocal这个变量+1呢

public void addNumByThreadLocal() {
  numLocal2.set(numLocal2.get() + 1);
}

那我们在main函数里编写我们的业务逻辑,看看最终的效果吧

public static void main(String[] args) {
  ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo();
  for (int i = 1; i <= 5; i++) {
    new Thread(() -> {
      int count = new Random().nextInt(5) + 1;
      try {
        for (int j = 0; j < count; j++) {
          // 由于操作的是主内存上的变量,所以共享
          threadLocalDemo.addNum();
          // 由于每个线程独立拥有副本,可以用来统计每个线程分别操作了多少次
          threadLocalDemo.addNumByThreadLocal();
        }
        System.out.println("线程" + Thread.currentThread().getName() + "调用了" + threadLocalDemo.numLocal2.get() + "次");
      } finally {
        // 为避免内存泄露问题,一定要remove,尤其在线程池中,会出现线程复用的问题,所以一定要remove
        threadLocalDemo.numLocal2.remove();
      }
    }, String.valueOf(i)).start();
  }
  try {
    TimeUnit.MILLISECONDS.sleep(500);
  } catch (InterruptedException e) {
    e.printStackTrace();
  }
  System.out.println(Thread.currentThread().getName() + "中此时num的值为:" + threadLocalDemo.num);
}

结果如下:

线程1调用了1
线程4调用了1
线程3调用了2
线程5调用了5
线程2调用了5
main中此时num的值为:14



03


Thread、ThreadLocal、ThreadLocalMap的关系



我们一起来看看这些类的源码

Thread.class

/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */

ThreadLocal.ThreadLocalMap threadLocals = null;

这也说明,对于每个线程,由ThreadLocal来维护自己的变量值

ThreadLocal.class

查看ThreadLocal的源码可以看到,ThreadLocal类有一个静态内部类ThreadLocalMap

static class ThreadLocalMap {

  /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */

  static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
      super(k);
      value = v;
    }
  }
  ...
}

ThreadLocalMap

回答问题二:ThreadLocalMap里维护的是一个Entry,类似字典的一个结构,我们可以看到,键是ThreadLocal,而值是一个Object,同时Entry继承了一个弱引用,这个问题会在后面进行介绍

类图

ThreadLocal看完你就明白了(一)
image-20220821201015465

回答问题一:threadLocalMap实际上是一个以threadLocal实例为key,任意对象为value的Entry对象

当我们为threadLocal变量赋值,实际上就是以当前threadLocal实例为key,值为value的Entry往这个threadLocalMap中存放

它们三者之间的关系总结一句话就是

对于每个Thread,都有ThreadLocal来维护自己线程的变量,ThreadLocal里面的静态内部类ThreadLocalMap用来存储变量值,键为threadLocal实例,值为任意对象



04


总结



好啦,今天小为就和大家分享到这,关于ThreadLocal剩余的内容,例如弱引用带来的问题、内存泄露等小为打算放到下一篇,总结一下,今天我们了解到了ThreadLocal的定义、Thread的使用方式以及常用的一些api还有Thread、ThreadLocal以及ThreadLocalMap三者的关系,那我们下期再见啦~~

关注六只栗子,面试不迷路。

作者    栗子为

编辑   一口栗子  



ThreadLocal看完你就明白了(一)

ThreadLocal看完你就明白了(一)

ThreadLocal看完你就明白了(一)

ThreadLocal看完你就明白了(一)


原文始发于微信公众号(六只栗子):ThreadLocal看完你就明白了(一)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/88363.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!