• 正确理解ThreadLocal:ThreadLocal中的值并不一定是完全隔离的


    首先再讨论题主的这个观点之前我们要明确一下ThreadLocal的用途是什么?

    ThreadLocal并不是用来解决共享对象的多线程访问问题。

    看了许多有关ThreadLocal的博客,看完之后会给人一种错觉,ThreadLocal就是用于在多线程情况下防止共享对象的线程安全问题,使用ThreadLocal之后,ThreadLocal的对象就不会有线程安全问题,但是一定是这样么,看如下代码

    1.  
      public class test {
    2.  
      public static void main(String[] args) throws InterruptedException {
    3.  
      new A().start();
    4.  
      new A().start();
    5.  
      new A().start();
    6.  
      new A().start();
    7.  
      }
    8.  
       
    9.  
      static class A extends Thread {
    10.  
      static List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
    11.  
      static ThreadLocal<List<Integer>> threadLocal = new ThreadLocal<List<Integer>>() {
    12.  
      @Override
    13.  
      protected List<Integer> initialValue() {
    14.  
      return list;
    15.  
      }
    16.  
      };
    17.  
       
    18.  
      @Override
    19.  
      public void run() {
    20.  
      List<Integer> threadList = threadLocal.get();
    21.  
      threadList.add(threadList.size());
    22.  
      System.out.println(threadList.toString());
    23.  
      }
    24.  
       
    25.  
      }
    26.  
      }

    该代码很简单,就是在多线程的情况下输出ThreadLocal的list集合状态,如果此时线程安全的话,输出的4个语句应该是完全一样的输出结果如下:

    [1, 2, 3, 4, 5, 5]
    [1, 2, 3, 4, 5, 5, 6]
    [1, 2, 3, 4, 5, 5, 6, 7]

    [1, 2, 3, 4, 5, 5, 6, 7, 8]

    很明显可以看到,线程是不安全的,从结果也能看出来4个线程中ThreadLocal中的List是同一个对象,被四个线程所共享。

    接下来我们分析一下原因的发生原因,我们去看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.  
      }

    如果一个线程第一次调用threadLocal.get()方法时,我们通过调试可以发现此时拿到的map是null,会调用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.  
      }

    可以看到这个方法里面有一个value,而这个value就是ThreadLocal中即将要保存的只对线程所见的"副本",而这个value使用过initialValue()方法得到的,这个方法如果你没有重写的话默认返回时一个null,而在我们的例子当中他返回的是我们定义的静态变量list,而这个list是一个单例的(只会被类第一次访问时进行初始化),也就是说我们的例子中每个线程的ThreadLocal里面get的都是同一个list,所以就造成了线程安全的问题.

    接下来改善一下我们的代码

    1.  
      static class A extends Thread {
    2.  
      static ThreadLocal<List<Integer>> threadLocal = new ThreadLocal<List<Integer>>() {
    3.  
      @Override
    4.  
      protected List<Integer> initialValue() {
    5.  
      return new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
    6.  
      }
    7.  
      };
    8.  
       
    9.  
      @Override
    10.  
      public void run() {
    11.  
      List<Integer> threadList = threadLocal.get();
    12.  
      threadList.add(threadList.size());
    13.  
      System.out.println(threadList.toString());
    14.  
      }
    15.  
       
    16.  
      }

    运行结果

    [1, 2, 3, 4, 5, 5]
    [1, 2, 3, 4, 5, 5]

    [1, 2, 3, 4, 5, 5]

    [1, 2, 3, 4, 5, 5]

    接下来回到主题,ThreadLocal的作用是什么:

    ThreadLocal使得各线程能够保持各自独立的一个对象,而实现原理其实是通过,每个线程都会重新创建一个对象,不是什么对象的拷贝或副本,而线程是否安全取决于你如何去创建这个对象。

    然后总结一下:

    1.ThreadLocal作用不是为了解决共享对象的多线程安全问题,而是为了避免通多参数传递的方式去拿到一个对象,网上有些例子就一开始拿线程安全举例子,然后抛砖引玉出ThreadLocal,会把人带偏。。。博主就是一个。

    2.ThreadLocal中保存的不是什么对象的副本,里面没有需要保存的对象做任何复制,拷贝操作,这个对象完全取决于你的iniialtValue方法中如何去创建,所以这里需要考虑使用ThreadLocal的性能问题,是否会大量创建一个对象。

  • 相关阅读:
    vue 之循环添加不同class
    小程序 之使用HMACSHA1算法加密报文
    微信小程序 之wx.getLocation()获取地理信息中的小坑
    js 时间戳与yyyy-mm-dd或yyyy-MM-dd HH-mm-ss互相转换
    小程序 之登录 wx.login()
    打开串口(COM)号大于9时报错
    linux的mysql权限错误导致看不到mysql数据库
    Nginx报错汇总
    获取磁盘总空间和剩余空间
    关于CoCreateInstance的0x800401f0问题
  • 原文地址:https://www.cnblogs.com/moxiaotao/p/9632914.html
Copyright © 2020-2023  润新知