Thread-Specific Storage
模式:即便只有一个入口,内部也会为每一个线程分配特有的存储空间,线程间 没有共享资源ThreadLocal
的用法与实现细节,但愿能帮上忙ThreadLocal 思惟导图java
线程安全 示意图android
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()
才会设置初值ThreadLocal#remove()
用于 移除 变量以前存储的值。若是在当前线程下次调用 ThreadLocal#get()
时,尚未 set()
新的值,依旧会使用 ThreadLocal#setInitialValue()
设置初始值。并发
到这里咱们就能够总结 ThreadLocal
的生命周期,以下图所示:less
ThreadLocal生命周期 示意图ssh
记得吗?《阿里巴巴Java开发手册》中提到过关于 ThreadLocal
的编程规约,以下所示:ide
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
的内部,探讨数据结构的实现细节。
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方法有提过。