共享模型之管程(5)

简介: 共享模型之管程

共享模型之管程(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 原理
模式方面
同步模式之保护性暂停
异步模式之生产者消费者
同步模式之顺序控制
相关文章
|
5月前
共享模型之管程(4)
共享模型之管程
25 0
|
5月前
|
消息中间件 Java API
共享模型之管程(2)
共享模型之管程
32 0
|
5月前
|
存储 安全 Java
共享模型之管程(1)
共享模型之管程
25 0
|
5月前
|
Java
共享模型之管程(3)
共享模型之管程
44 0
|
Linux 编译器 调度
【线程概念和线程控制】(一)
【线程概念和线程控制】(一)
123 0
|
存储
【线程概念和线程控制】(二)
【线程概念和线程控制】(二)
66 0
|
存储 Linux 调度
Linux多线程:线程概念、线程间的独有与共享、多线程VS多进程,线程控制:线程创建、线程终止、线程等待、线程分离
Linux多线程:线程概念、线程间的独有与共享、多线程VS多进程,线程控制:线程创建、线程终止、线程等待、线程分离
230 0
C#编程-147:线程基础
C#编程-147:线程基础
109 0
C#编程-147:线程基础
|
安全 Java C++
【Java并发编程系列5】对象的共享
在没有Java相关并发知识的前提下,第一次看这本书《Java并发编程实战》,其实有些看不太懂,因为里面的很多知识讲的比较抽象,比如可见性、volatile、final等讲的其实都不深入,所以导致自己理解的也很片面。后来就先专门看了“Java内存模型”相关的知识,再对相关知识理解起来,就要深入一些,所以才有了前面写的4篇关于“Java内存模型”相关的文章。
164 0
【Java并发编程系列5】对象的共享
|
消息中间件
进程同步的方式有哪些
进程同步的方式有哪些
283 0