美文网首页
ThreadLocal实现原理揭秘

ThreadLocal实现原理揭秘

作者: 荆辰曦 | 来源:发表于2019-03-11 17:15 被阅读0次

ThreadLocal是什么?
对java多线程有了解的人都清楚,在多个线程程序中,会出现线程安全性问题,即多线程是不安全的,线程不安全的场景有很多,这里不一一赘述,这里重点是多线程不安全的场景之一————类成员变量在多线程环境下是不安全的。举例如下:

package testpackage;

import lombok.Data;

@Data
public class Class2 {

  private int i;

  private ThreadLocal<Integer> j = new ThreadLocal<>();
}

//------------------------万恶的分割线----------------------------------------

package testpackage;

public class Class1 {
  
  public static void main(String[] args) {
   Class2 class2 = new Class2();
   class2.setI(1);
    new Thread(()->{
       System.out.println("子线程开始");
       class2.setI(10);
       System.out.println("子线程 i=" + class2.getI());
       System.out.println("子线程结束");
    }).start();
    System.out.println("主线程休眠3秒");
    try {
      Thread.sleep(3000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("主线 i ="+class2.getI());
    System.out.println("主结束");
  }

}

int类型的i为Class2的类成员变量,Class1里有两个线程,一个为主线程,一个为从主线程新开的子线程,假定两个线程都需要使用i,理想状态是:主线程与子线程互不干扰,即变量i在两个线程中应该是独立互不干扰的(就好像人民币是文明社会人类共有的概念【定义的变量i】,但是每个人拥有的人民币却是他们自己的),上述代码运行结果如下:

主线程休眠3秒
子线程开始
子线程 i=10   --重点
子线程结束
主线 i =10    --重点
主结束

发现了么?主线程的 i 结果为10,按理想状态,变量 i 在子线程中改为了10,但这个改动应与主线程无关,因为这是两个线程,两个线程间的应该互不干扰才是线程安全的,但上述代码实际运行情况是子线程修改了 i 的值后,对主线程的 i的使用产生了影响。
上述现象既是开篇提到的 类成员变量在多线程环境下是不安全的

那么,问题来了,我如果需要使用线程安全的变量该怎么办呢?这个问题的答案就是————ThreadLocal,使用ThreadLocal类型的变量即可使其线程安全,具体使用方法如下:

package testpackage;

import lombok.Data;

@Data
public class Class2 {

  private int i;

  private ThreadLocal<Integer> j = new ThreadLocal<>();
}

//------------------------万恶的分割线----------------------------------------

package testpackage;

public class Class1 {

  public static void main(String[] args) {
   Class2 class2 = new Class2();
  //主线程设置 i 的值为1
   class2.setI(1);
  //主线程设置 j 的值为1
   class2.getJ().set(1);
    new Thread(()->{
       System.out.println("子线程开始");
       class2.setI(10);
       class2.getJ().set(11);
       System.out.println("子线程 i=" + class2.getI());
       System.out.println("子线程 j=" + class2.getJ().get());
       System.out.println("子线程结束");
    }).start();
    System.out.println("主线程休眠3秒");
    try {
      Thread.sleep(3000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("主线 i ="+class2.getI());
    System.out.println("子线程 j=" + class2.getJ().get());
    System.out.println("主结束");
  }

}

上述代码的运行情况如下:

主线程休眠3秒
子线程开始
子线程 i=10
子线程 j=11
子线程结束
主线 i =10
主线程 j=1
主结束

结果证明,ThreadLocal的使用保证了变量在多线程中的安全性。

那么,ThreadLocal是如何实现变量线程间的安全性的呢,是如何保证变量在多个线程间独立互不干扰呢?现在,就开始对ThreadLocal的实现原理逐一分析:

首先,从Thread说起:

public
class Thread implements Runnable {
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
}
public class ThreadLocal<T> {
 /**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
    static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    }
}

由此,可以得到ThreadLocal内部结构图如下:


image.png

从上面的结构图,我们已经窥见ThreadLocal的核心机制:

  • 每个Thread线程内部都有一个ThreadLocalMap。
  • ThreadLocalMap里面存储ThreadLocal对象(key)和线程的变量副本(value)
  • 但是,Thread内部的ThreadLocalMap是由ThreadLocal维护的,由ThreadLocal负责向ThreadLocalMap获取和设置线程的变量值。

所以对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。

ThreadLocal的主要方法解析:

1.ThreadLocal主要有以下方法
public T get()
public void set(T value)
public void remove()
  • get()方法用于获取当前线程的副本变量值。
  • set()方法用于保存当前线程的副本变量值。
  • remove()方法移除当前前程的副本变量值。

我们先来看看 get 方法:

public
class Thread implements Runnable {
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

 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;
            }
        }
    }
  private Entry[] table;

  private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
    }

  }
}
//---------------------------------------万恶的分割线----------------------------------------------

/**
get方法就做了如下几件事:
1.获取当前线程
2.拿到当前线程Thread对象后,拿到线程类成员threadLocals
3.调用getEntry以ThreadLocal
**/
public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
}

private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
 }

 void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
 }

相关文章

  • ThreadLocal实现原理揭秘

    ThreadLocal是什么?对java多线程有了解的人都清楚,在多个线程程序中,会出现线程安全性问题,即多线程是...

  • 深入ThreadLocal

    主要内容 ThreadLocal 基本操作 实现原理 前言 本文主要深入了解ThreadLocal的内部实现原理,...

  • 阿里架构师教你如何使用ThreadLocal及原理分析

    内容导航 什么是ThreadLocal ThreadLocal的使用 分析ThreadLocal的实现原理 Thr...

  • Java - ThreadLocal详细讲解

    ThreadLocal常用来做线程隔离,下面将对ThreadLocal的实现原理、设计理念、内部实现细节(Map、...

  • ThreadLocal 分析

    ThreadLocal 作用:实现数据隔离 ThreadLocal 原理分析: 每个线程Thread都维护了自己的...

  • ThreadLocal深入解析

    ThreadLocal实现原理 1.原理ThreadLocal本身是不对数据进行存储的,真正存储的数据的地方还是线...

  • 第二十六周 ThreadLocal

    话题:ThreadLocal 的作用和实现原理 关键字:艺术探索 参考答案 Android ThreadLocal...

  • ThreadLocal剖析

    ThreadLocal可以在多线程下实现各个线程的数据隔离 存储原理 直接看ThreadLocal的get()方法...

  • ThreadLocal

    ThreadLocal 简介ThreadLocal 使用ThreadLocal 原理InheritableThre...

  • spring事务管理

    原理是通过ThreadLocal实现的,看源码 先从transactionTemplate.execute开始 获...

网友评论

      本文标题:ThreadLocal实现原理揭秘

      本文链接:https://www.haomeiwen.com/subject/wwebpqtx.html