`

ThreadLocal及InheritableThreadLocal的原理剖析

阅读更多

我们知道,线程的不安全问题,主要是由于多线程并发读取一个变量而引起的,那么有没有一种办法可以让一个变量是线程独有的呢,这样不就可以解决线程安全问题了么。其实JDK已经为我们提供了ThreadLocal这个东西。


ThreadLocal基本使用

当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
ThreadLocal 的主要方法有这么几个:

1
2
3
4
initialValue 初始化
set 赋值
get 取值
remove 清空

下面来看一个简单的使用代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ThreadLocalDemo {
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        public Integer initialValue() {
            return 0;
        }
    };
    static class ThreadDemo implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                threadLocal.set(threadLocal.get() + 1);
            }
            System.out.println("thread :" + Thread.currentThread().getId() + " is" + threadLocal.get());
        }
    }
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(new ThreadDemo()).start();
        }
    }
}

上方代码使用了10个线程循环对一个threadLocal的值进行一千次的加法,如果我们不知道ThreadLocal的原理的话我们可能会觉得最后打印的值一定是1000、2000、3000。。10000或者是线程不安全的值。
但是如果你执行这段代码你会发现最后打印的都是1000。


ThreadLocal原理剖析

现在我们来看一下ThreadLocal是如何实现为每个线程单独维护一个变量的呢。
先来看一下初始化方法。

1
2
3
protected T initialValue() {
        return null;
    }

initialValue 默认是返回空的,所以为了避免空指针问题重写了这个方法设置了默认返回值为0,但是呢,虽然这个方法好像是设置默认值的,但是还没有生效,具体请接着往下看。

1
2
3
4
5
6
7
8
public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

我们可以看到set方法首先会获取当前线程,然后通过一个getMap方法获取了ThreadLocalMap,接着来看一下这个map是怎么来的呢。

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

这个map是在Thread类维护的一个map,下方是Thread类维护的此变量。默认这个map是空的。

1
ThreadLocal.ThreadLocalMap threadLocals = null;

接着往下看代码,如果获取的时候map不为空,则通过set方法把Thread类的threadLocals变量更新。如果是第一次创建的时候则初始化Thread的threadLocals变量。
下方是createMap的代码:

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

接下来看个get方法就比较容易理解了。

1
2
3
4
5
6
7
8
9
10
11
12
13
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();
    }

注意关注最后的一个return,看到调用的这个方法名我们就可以发现这个ThreadLocal的初始化原来是当第一调用get方法时如果还没有被set的时候才会去获取initialValue 方法的返回值。

1
2
3
4
5
6
7
8
9
10
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;
    }


使用ThreadLocal最应该注意的事项

首先来看一下线程退出的办法:

1
2
3
4
5
6
7
8
9
10
11
12
private void exit() {
        if (group != null) {
            group.threadTerminated(this);
            group = null;
        }
        target = null;
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }

我们看到当线程结束的时候上方第7行会把ThreadLocal的值制为空,这个东西本身是没问题的。但是,如果你是使用的线程池,这个问题可就大了!!!
要知道线程池里的线程执行完一个任务之后紧接着下一个,这中间线程可不会结束,下一个任务获得Thread的值可是上一个任务的遗留数据。
下面是这个问题的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        public Integer initialValue() {
            return 0;
        }
    };
    static class ThreadDemo implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                threadLocal.set(threadLocal.get() + 1);
            }
            System.out.println("thread :" + Thread.currentThread().getId() + " is" + threadLocal.get());
            //threadLocal.remove();
        }
    }
    public static void main(String[] args) {
        ExecutorService executorService= Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            executorService.submit(new Thread(new ThreadDemo()));
        }
    }

执行这段代码你就会发现同样的操作在线程池里已经得不到一样的结果了。想要解决这种问题也很简单,只需要把ThreadLocal的值在线程执行完清空就可以了。把第14行注释的代码放开再执行以下你就明白了。


InheritableThreadLocal

其实ThreadLocal还有一个比较强大的子类InheritableThreadLocal,它呢可以把父线程生成的变量传递给子线程。
下面来看一下代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class InheritableThreadLocalDemo {
    private static  InheritableThreadLocal<Integer> inheritableThreadLocal = new  InheritableThreadLocal<Integer>();
    static class ThreadDemo implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                inheritableThreadLocal.set(inheritableThreadLocal.get() + 1);
            }
            System.out.println("thread :" + Thread.currentThread().getId() + " is" + inheritableThreadLocal.get());
        }
    }
    public static void main(String[] args) {
        inheritableThreadLocal.set(24);
        for (int i = 0; i < 10; i++) {
            new Thread(new ThreadDemo()).start();
        }
    }
}

执行代码会发现程序输出全是1024,这就是因为InheritableThreadLocal吧在主线程设置的值24传递到了那10个子线程中。


InheritableThreadLocal原理剖析

接下来我们来看一下InheritableThreadLocal为什么可以实现这种功能呢。
InheritableThreadLocal是ThreadLocal的子类,
与ThreadLocal相同的set方法

1
2
3
4
5
6
7
8
public void set(T value) {
       Thread t = Thread.currentThread();
       ThreadLocalMap map = getMap(t);
       if (map != null)
           map.set(this, value);
       else
           createMap(t, value);
   }

不同点是InheritableThreadLocal重写了createMap方法,将值赋值给了线程的inheritableThreadLocals变量。

1
2
3
void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }

再跟进去Thread类的源码看inheritableThreadLocals变量你会发现:我去,这不是跟Threadlocal一样么,同样初始值为null,线程退出的时候清空。没错,就是这样的。也就是说它其实也是一个线程私有的变量,ThreadLocal的功能它是都有的。

那么它又是怎么把父线程的变量传递到子线程的呢?
接着看Thread的构造方法

1
2
3
public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

一路追踪init方法你会看见这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
        this.name = name;
        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            if (security != null) {
                g = security.getThreadGroup();
            }
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }
        g.checkAccess();
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }
        g.addUnstarted();
        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        this.stackSize = stackSize;
        tid = nextThreadID();
    }

仔细观察倒数第5行到倒数第二行你就明白了。

本文所有源码https://github.com/shiyujun/syj-study-demo

 

 



 

  • 大小: 171.6 KB
0
0
分享到:
评论

相关推荐

    Android 详解ThreadLocal及InheritableThreadLocal

    ThreadLocal和InheritableThreadLocal是Java中两个非常重要的线程相关的类,它们在Android开发中也有广泛应用。...理解ThreadLocal和InheritableThreadLocal的工作原理对于编写健壮的多线程代码至关重要。

    ThreadLocal、InheritableThreadLocal详解

    ThreadLocal和InheritableThreadLocal是Java中处理线程局部变量的两个类,它们主要用于解决多线程环境下的数据隔离问题。线程安全是并发编程中的核心问题之一,而ThreadLocal提供了一种机制,使得每个线程都有自己的...

    InheritableThreadLocal & ThreadLocal

    《InheritableThreadLocal & ThreadLocal 深度解析》 在Java编程中,线程局部变量(ThreadLocal)和可继承线程局部变量(InheritableThreadLocal)是两种非常重要的工具,它们允许我们在多线程环境中创建独立于线程...

    ThreadLocal垮线程池传递数据解决方案.docx

    TransmittableThreadLocal 的实现原理是基于ThreadLocal 和InheritableThreadLocal 的结合。它可以在多个线程池之间传递数据,实现了跨线程池之间的数据传递。 使用TransmittableThreadLocal 可以解决多种线程池...

    ThreadLocal的原理,源码深度分析及使用.docx

    ThreadLocal 的原理、源码深度分析及使用 ThreadLocal 是 Java 语言中的一种机制,用于实现线程本地存储,能够帮助开发者在多线程环境下解决变量访问安全的问题。下面将对 ThreadLocal 的原理、实现机制、使用方法...

    ThreadLocal原理及在多层架构中的应用

    ThreadLocal的工作原理主要基于以下几点: - **内部类ThreadLocalMap**:ThreadLocal在每个线程内部维护了一个名为ThreadLocalMap的哈希映射表,这个表的键是ThreadLocal对象,值是线程局部变量的实际值。这样,每...

    ThreadLocal_ThreadLocal源码分析_

    **ThreadLocal概述** ThreadLocal是Java中的一个线程局部变量类,它...通过理解ThreadLocal的原理和最佳实践,我们可以更有效地利用它来解决多线程环境下的数据隔离问题,同时也需要注意防止可能出现的内存泄漏风险。

    ThreadLocal原理及在多层架构中的应用.pdf

    首先,ThreadLocal原理是基于每个线程创建一个私有的数据存储结构(ThreadLocalMap),使得线程可以拥有自己独立的数据副本,而不会与其他线程共享。这意味着,即使多个线程访问相同的变量,它们也各自拥有这个变量...

    ThreadLocal应用示例及理解

    ### 理解ThreadLocal原理 ThreadLocal内部通过一个ThreadLocalMap来存储每个线程的副本。这个Map的键是ThreadLocal实例,值是线程的局部变量。每个线程都有自己的ThreadLocalMap,存储在Thread类的成员变量中。 ##...

    ThreadLocal原理分析图,visio软件打开

    ThreadLocal原理分析图,visio软件打开

    ThreadLocal 内存泄露的实例分析1

    at 中专门为每一个 web 应用...理解 `ThreadLocal` 的工作原理以及它如何与类加载器交互,是避免此类问题的关键。在实际开发中,应当养成良好的编程习惯,如使用后及时清理 `ThreadLocal` 变量,以防止内存资源的浪费。

    ThreadLocal

    在Java中,ThreadLocal的工作原理是为每个线程创建一个单独的存储空间,每个线程可以独立地读写这个变量,而不会影响其他线程。当我们创建一个新的ThreadLocal实例时,它并不会立即分配内存,而是等到线程第一次调用...

    深入理解 Java 之 ThreadLocal 工作原理1

    ThreadLocal是Java中用于线程局部变量的一个工具类,它的工作原理主要体现在如何在不同的线程之间隔离变量的副本,确保每个线程拥有自己的独立数据。这个设计模式在多线程编程中尤其有用,因为它避免了传统的同步...

    ThreadLocal_ThreadLocal源码分析_源码.zip

    **ThreadLocal内部实现原理** ThreadLocal的核心在于它的内部类`ThreadLocalMap`,这个类实际上是线程内部的一个`HashMap`。每个Thread对象都有一个`ThreadLocalMap`成员,用于存储线程局部变量。键是ThreadLocal...

    java 简单的ThreadLocal示例

    因此,理解ThreadLocal的工作原理并谨慎使用是非常重要的。 在提供的"ThreadLocal示例"压缩包中,可能包含了一些具体的代码示例,展示如何在实际项目中运用ThreadLocal。通过查看这些示例,你可以更深入地理解...

    深入理解ThreadLocal工作原理及使用示例

    深入理解ThreadLocal工作原理及使用示例 ThreadLocal是Java提供的一种解决多线程程序并发问题的工具类,自JDK1.2版本以来提供了java.lang.ThreadLocal类。ThreadLocal的主要作用是为每个使用该变量的线程提供独立的...

Global site tag (gtag.js) - Google Analytics