• Java线程本地存储ThreadLocal


    前言

    • ThreadLocal 是一种 无同步 的线程安全实现
    • 体现了 Thread-Specific Storage 模式:即使只有一个入口,内部也会为每个线程分配特有的存储空间,线程间 没有共享资源
    • 本文将总结 ThreadLocal 的用法与实现细节,希望能帮上忙

    ThreadLocal 思维导图

    线程安全 示意图

    1. 用法

    ThreadLocal 的用法很简单, ThreadLocal 提供了下列的public与protected方法,本文将引用 android.os.Looper.java 为例子讲解 ThreadLocal 的用法:

    ThradlLocal UML类图

    // /frameworks/base/core/java/android/os/Looper.java
    
    public class Looper {
    
        // ...
    
        // sThreadLocal.get() will return null unless you've called prepare().
        static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
        private static void prepare(boolean quitAllowed) {
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            // 设置线程局部变量的值
            sThreadLocal.set(new Looper(quitAllowed));
        }
    
        public static Looper myLooper() {
            // 获取线程局部变量的值
            return sThreadLocal.get();
        }
    
        public static void prepare() {
            prepare(true);
        }
    
        // ...
    }
    • ThreadLocal 为 static final变量 ,泛型参数为 Looper ,表示接受 Looper 类型的值
    • Looper#prepare() 中调用 ThreadLocal#set() 设置 变量的值,不同线程设置的值互不干扰,不会相互覆盖
    • Looper#myLooper() 中调用 ThreadLocal#get() 获取 变量的值,不同线程获取的值互不干扰

    Timethreads图 - 01

    • 子类 重写 ThreadLocal#initialValue() ,可以设置变量的初始值,我们查看相关源码:

      public ConcurrentHashMap() {
          this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
      }
      public ConcurrentHashMap(int initialCapacity,
                                   float loadFactor, int concurrencyLevel) {
          if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
              throw new IllegalArgumentException();
          if (concurrencyLevel > MAX_SEGMENTS)
              concurrencyLevel = MAX_SEGMENTS;
          // Find power-of-two sizes best matching arguments
          int sshift = 0;
          int ssize = 1;
          while (ssize < concurrencyLevel) {
              ++sshift;
              ssize <<= 1;
          }
          this.segmentShift = 32 - sshift;//用于高位,判断落在哪个Segment
          this.segmentMask = ssize - 1;//用于取模。之前在hashmap的indexFor方法有提过。2的n次方-1
          if (initialCapacity > MAXIMUM_CAPACITY)
              initialCapacity = MAXIMUM_CAPACITY;
          int c = initialCapacity / ssize;
          if (c * ssize < initialCapacity)
              ++c;
          int cap = MIN_SEGMENT_TABLE_CAPACITY;
          while (cap < c)
              cap <<= 1;
          // create segments and segments[0]
          Segment<K,V> s0 =
              new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
                               (HashEntry<K,V>[])new HashEntry[cap]);//初始化第一个位置的Segment
          Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];//初始化Segments
          UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
          this.segments = ss;
      }
      • 在 ThreadLocal#get() 中尝试获取变量的值,如果为空则会调用 ThreadLocal#setInitialValue() 设置设置初始值
      • 懒初始化 :直到第一次调用 get() 才会设置初值
      • 默认 :设置初始值为 null
    • ThreadLocal#remove() 用于 移除 变量之前存储的值。如果在当前线程下次调用 ThreadLocal#get() 时,还没有 set() 新的值,依旧会使用 ThreadLocal#setInitialValue() 设置初始值。

    到这里我们就可以总结 ThreadLocal 的生命周期,如下图所示:

    ThreadLocal生命周期 示意图

    2. 编程规约

    记得吗?《阿里巴巴Java开发手册》中提到过关于 ThreadLocal 的编程规约,如下所示:

    • 5.【强制】 SimpleDateFormate 是线程不安全的类,一般不要定义为 static 变量,如果定义为static,必须加锁,或者使用 DateUtils 工具类

      正例:

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

      说明:如果是JDK8的应用,可以使用 Instant 代替 Date , LocalDateTime 代替 Calendar, DateTimeFormatter 代替 SimpleDateFormat ,官方给出的解释:simple beautiful strong immutable thread-safe.

    • 15.【参考】(原文过于啰嗦,以下为笔者转述) ThreadLocal 变量建议使用 static 修饰,可以保证变量在类初始化时创建,所有类实例可以共享同一个静态变量。

      注意到了吗?在文章开头的Looper.java源码中, ThreadLocal 变量就是使用 static 修饰的

    看到这里,相信你已经掌握了 ThreadLocal 的用法,下一篇文章将深入 ThreadLocal 的内部,探讨数据结构的实现细节。

    Segment

    ConcurrentHashMap是由多个Segment组成的,Segment继承了ReentrantLock,每次加锁都是对某个Segment,不会影响其他Segment,达到了锁分离(也叫分段锁)的作用。

    每个Segment又包含了HashEntry数组,HashEntry是一个链表。如下图所示:

    concurrencyLevel:并发数,默认16,直接影响segmentShift和segmentMask的值,以及Segment的初始化数量。Segment初始化的数量,为最接近且大于的办等于2的N次方的值,比如concurrencyLevel=16,Segment数量为16,concurrencyLevel=17,Segment数量为32。segmentShift的值是这样的,比如Segment是32,相对于2的5次方,那么他的值就是32-5,为27,后面无符号右移27位,也就是取高5位的时候,就是0到31的值,此时Segment的下标也是0到31,取模后对应着每个Segment。segmentMask就是2的n次方-1,这边n是5,用于取模。之前在hashmap的indexFor方法有提过。

  • 相关阅读:
    IOS添加pch预编译文件
    UITableview控件基本使用
    两种单例模式的写法
    提交app Store审核时,Missing 64-bit support问题的解决办法
    IOS开发NSString与int和float的相互转换以及字符串拼接、NSString、NSData、char* 类型之间的转换
    获取UITableView每行中不同的UITextField输入的内容(例如修改登陆密码)
    UITextField常用属性归纳:文本框样式、文字样式、键盘样式、左右视图样式、清除按钮设置等,iosuitextfield
    UITabBarController(标签栏控制器)
    iOS基础控件UINavigationController中的传值
    IOS开发之多线程
  • 原文地址:https://www.cnblogs.com/aishangJava/p/11267562.html
Copyright © 2020-2023  润新知