共享模型之管程(4)https://developer.aliyun.com/article/1530879
14、ThreadLocal
简介
ThreadLocal是JDK包提供的,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题
使用
public class ThreadLocalStudy { public static void main(String[] args) { // 创建ThreadLocal变量 ThreadLocal<String> stringThreadLocal = new ThreadLocal<>(); ThreadLocal<User> userThreadLocal = new ThreadLocal<>(); // 创建两个线程,分别使用上面的两个ThreadLocal变量 Thread thread1 = new Thread(()->{ // stringThreadLocal第一次赋值 stringThreadLocal.set("thread1 stringThreadLocal first"); // stringThreadLocal第二次赋值 stringThreadLocal.set("thread1 stringThreadLocal second"); // userThreadLocal赋值 userThreadLocal.set(new User("Nyima", 20)); // 取值 System.out.println(stringThreadLocal.get()); System.out.println(userThreadLocal.get()); // 移除 userThreadLocal.remove(); System.out.println(userThreadLocal.get()); }); Thread thread2 = new Thread(()->{ // stringThreadLocal第一次赋值 stringThreadLocal.set("thread2 stringThreadLocal first"); // stringThreadLocal第二次赋值 stringThreadLocal.set("thread2 stringThreadLocal second"); // userThreadLocal赋值 userThreadLocal.set(new User("Hulu", 20)); // 取值 System.out.println(stringThreadLocal.get()); System.out.println(userThreadLocal.get()); }); // 启动线程 thread1.start(); thread2.start(); } } class User { String name; int age; public User(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } }Copy
运行结果
thread1 stringThreadLocal second thread2 stringThreadLocal second User{name='Nyima', age=20} User{name='Hulu', age=20} nullCopy
从运行结果可以看出
- 每个线程中的ThreadLocal变量是每个线程私有的,而不是共享的
- 从线程1和线程2的打印结果可以看出
- ThreadLocal其实就相当于其泛型类型的一个变量,只不过是每个线程私有的
- stringThreadLocal被赋值了两次,保存的是最后一次赋值的结果
- ThreadLocal可以进行以下几个操作
- set 设置值
- get 取出值
- remove 移除值
原理
Thread中的threadLocals
public class Thread implements Runnable { ... ThreadLocal.ThreadLocalMap threadLocals = null; // 放在后面说 ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; ... }Copy static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }Copy
可以看出Thread类中有一个threadLocals和一个inheritableThreadLocals,它们都是ThreadLocalMap类型的变量,而ThreadLocalMap是一个定制化的Hashmap。在默认情况下,每个线程中的这两个变量都为null。此处先讨论threadLocals,inheritableThreadLocals放在后面讨论
ThreadLocal中的方法
set方法
public void set(T value) { // 获取当前线程 Thread t = Thread.currentThread(); // 获得ThreadLocalMap对象 // 这里的get会返回Thread类中的threadLocals ThreadLocalMap map = getMap(t); // 判断map是否已经创建,没创建就创建并放入值,创建了就直接放入 if (map != null) // ThreadLocal自生的引用作为key,传入的值作为value map.set(this, value); else createMap(t, value); }Copy
如果未创建
void createMap(Thread t, T firstValue) { // 创建的同时设置想放入的值 // hreadLocal自生的引用作为key,传入的值作为value t.threadLocals = new ThreadLocalMap(this, firstValue); }Copy
get方法
public T get() { // 获取当前线程 Thread t = Thread.currentThread(); // 获取当前线程的threadLocals变量 ThreadLocalMap map = getMap(t); // 判断threadLocals是否被初始化了 if (map != null) { // 已经初始化则直接返回 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } // 否则就创建threadLocals return setInitialValue(); }Copy private T setInitialValue() { // 这个方法返回是null T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); // 无论map创建与否,最终value的值都为null if (map != null) map.set(this, value); else createMap(t, value); return value; }Copy protected T initialValue() { return null; }Copy
remove方法
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) // 如果threadLocals已经被初始化,则移除 m.remove(this); }Copy
总结
在每个线程内部都有一个名为threadLocals的成员变量,该变量的类型为HashMap,其中key为我们定义的ThreadLocal变量的this引用,value则为我们使用set方法设置的值。每个线程的本地变量存放在线程自己的内存变量threadLocals中
只有当前线程第一次调用ThreadLocal的set或者get方法时才会创建threadLocals(inheritableThreadLocals也是一样)。其实每个线程的本地变量不是存放在ThreadLocal实例里面,而是存放在调用线程的threadLocals变量里面
15、InheritableThreadLocal
简介
从ThreadLocal的源码可以看出,无论是set、get、还是remove,都是相对于当前线程操作的
Thread.currentThread()Copy
所以ThreadLocal无法从父线程传向子线程,所以InheritableThreadLocal出现了,它能够让父线程中ThreadLocal的值传给子线程。
也就是从main所在的线程,传给thread1或thread2
使用
public class Demo1 { public static void main(String[] args) { ThreadLocal<String> stringThreadLocal = new ThreadLocal<>(); InheritableThreadLocal<String> stringInheritable = new InheritableThreadLocal<>(); // 主线程赋对上面两个变量进行赋值 stringThreadLocal.set("this is threadLocal"); stringInheritable.set("this is inheritableThreadLocal"); // 创建线程 Thread thread1 = new Thread(()->{ // 获得ThreadLocal中存放的值 System.out.println(stringThreadLocal.get()); // 获得InheritableThreadLocal存放的值 System.out.println(stringInheritable.get()); }); thread1.start(); } }Copy
运行结果
null this is inheritableThreadLocalCopy
可以看出InheritableThreadLocal的值成功从主线程传入了子线程,而ThreadLocal则没有
原理
InheritableThreadLocal
public class InheritableThreadLocal<T> extends ThreadLocal<T> { // 传入父线程中的一个值,然后直接返回 protected T childValue(T parentValue) { return parentValue; } // 返回传入线程的inheritableThreadLocals // Thread中有一个inheritableThreadLocals变量 // ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; ThreadLocalMap getMap(Thread t) { return t.inheritableThreadLocals; } // 创建一个inheritableThreadLocals void createMap(Thread t, T firstValue) { t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); } }Copy
由如上代码可知,InheritableThreadLocal继承了ThreadLocal,并重写了三个方法。InheritableThreadLocal重写了createMap方法,那么现在当第一次调用set方法时,创建的是当前线程的inheritableThreadLocals变量的实例而不再是threadLocals。当调用getMap方法获取当前线程内部的map变量时,获取的是inheritableThreadLocals而不再是threadLocals
childValue(T parentValue)方法的调用
在主函数运行时,会调用Thread的默认构造函数(创建主线程,也就是父线程),所以我们先看看Thread的默认构造函数
public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); }Copy private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { ... // 获得当前线程的,在这里是主线程 Thread parent = currentThread(); ... // 如果父线程的inheritableThreadLocals存在 // 我们在主线程中调用set和get时,会创建inheritableThreadLocals if (inheritThreadLocals && parent.inheritableThreadLocals != null) // 设置子线程的inheritableThreadLocals this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); /* Stash the specified stack size in case the VM cares */ this.stackSize = stackSize; /* Set thread ID */ tid = nextThreadID(); }Copy static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); }Copy
在createInheritedMap内部使用父线程的inheritableThreadLocals变量作为构造函数创建了一个新的ThreadLocalMap变量,然后赋值给了子线程的inheritableThreadLocals变量
private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len]; for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null) { // 这里调用了 childValue 方法 // 该方法会返回parent的值 Object value = key.childValue(e.value); Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while (table[h] != null) h = nextIndex(h, len); table[h] = c; size++; } } } }Copy
在该构造函数内部把父线程的inheritableThreadLocals成员变量的值复制到新的ThreadLocalMap对象中
总结
InheritableThreadLocal类通过重写getMap和createMap,让本地变量保存到了具体线程的inheritableThreadLocals变量里面,那么线程在通过InheritableThreadLocal类实例的set或者get方法设置变量时,就会创建当前线程的inheritableThreadLocals变量。
当父线程创建子线程时,构造函数会把父线程中inheritableThreadLocals变量里面的本地变量复制一份保存到子线程的inheritableThreadLocals变量里面。
本章小结
本章我们需要重点掌握的是
分析多线程访问共享资源时,哪些代码片段属于临界区
使用 synchronized 互斥解决临界区的线程安全问题
掌握 synchronized 锁对象语法
掌握 synchronzied 加载成员方法和静态方法语法
掌握 wait/notify 同步方法
使用 lock 互斥解决临界区的线程安全问题
掌握 lock 的使用细节:可打断、锁超时、公平锁、条件变量
学会分析变量的线程安全性、掌握常见线程安全类的使用
了解线程活跃性问题:死锁、活锁、饥饿
应用方面
互斥:使用 synchronized 或 Lock 达到共享资源互斥效果
同步:使用 wait/notify 或 Lock 的条件变量来达到线程间通信效果
原理方面
monitor、synchronized 、wait/notify 原理
synchronized 进阶原理
park & unpark 原理
模式方面
同步模式之保护性暂停
异步模式之生产者消费者
` public Thread() { init(null, null, “Thread-” + nextThreadNum(), 0); }Copy private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {
…
// 获得当前线程的,在这里是主线程 Thread parent = currentThread(); ... // 如果父线程的inheritableThreadLocals存在 // 我们在主线程中调用set和get时,会创建inheritableThreadLocals if (inheritThreadLocals && parent.inheritableThreadLocals != null) // 设置子线程的inheritableThreadLocals this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); /* Stash the specified stack size in case the VM cares */ this.stackSize = stackSize; /* Set thread ID */ tid = nextThreadID();
}Copy static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); }Copy
在createInheritedMap内部使用父线程的inheritableThreadLocals变量作为构造函数创建了一个新的ThreadLocalMap变量,然后赋值给了子线程的inheritableThreadLocals变量
private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len];
for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null) { // 这里调用了 childValue 方法 // 该方法会返回parent的值 Object value = key.childValue(e.value); Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while (table[h] != null) h = nextIndex(h, len); table[h] = c; size++; } } }
}Copy
在该构造函数内部把父线程的inheritableThreadLocals成员变量的值复制到新的ThreadLocalMap对象中 #### 总结 InheritableThreadLocal类通过重写getMap和createMap,让本地变量保存到了具体线程的inheritableThreadLocals变量里面,那么线程在通过InheritableThreadLocal类实例的set或者get方法设置变量时,就会创建当前线程的inheritableThreadLocals变量。 **当父线程创建子线程时,构造函数会把父线程中inheritableThreadLocals变量里面的本地变量复制一份保存到子线程的inheritableThreadLocals变量里面。** # 本章小结 本章我们需要重点掌握的是 分析多线程访问共享资源时,哪些代码片段属于临界区 使用 synchronized 互斥解决临界区的线程安全问题 掌握 synchronized 锁对象语法 掌握 synchronzied 加载成员方法和静态方法语法 掌握 wait/notify 同步方法 使用 lock 互斥解决临界区的线程安全问题 掌握 lock 的使用细节:可打断、锁超时、公平锁、条件变量 学会分析变量的线程安全性、掌握常见线程安全类的使用 了解线程活跃性问题:死锁、活锁、饥饿 应用方面 互斥:使用 synchronized 或 Lock 达到共享资源互斥效果 同步:使用 wait/notify 或 Lock 的条件变量来达到线程间通信效果 原理方面 monitor、synchronized 、wait/notify 原理 synchronized 进阶原理 park & unpark 原理 模式方面 同步模式之保护性暂停 异步模式之生产者消费者 同步模式之顺序控制