TreadLocal模式的原理

  在JDK的早期版本中,提供了一种解决多线程并发问题的方案:java.lang.ThreadLocal类。ThreadLocal类在维护变量时,实际使用了当前线程(Thread)中的一个叫做ThreadLocalMap的独立副本,每个线程可以独立修改属于自己的副本而不会互相影响,从而隔离了线程和线程,避免了线程访问实例变量发生冲突的问题。

  ThreadLocal本身并不是一个线程,而是通过操作当前线程中的一个内部变量来达到与其他线程隔离的目的。之所以取名为ThreadLocal,所期望表达的含义是其操作的对象是线程的一个本地变量。

Thread.java

public class Thread implements Runnable {
    // 这里省略了许多其他的代码
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

ThreadLocal.java

public class ThreadLocal<T> {
    // 这里省略了许多其他代码
    // 将value 的值保存于当前线程的本地变量中
    public void set(T value) {
        // 获取当前线程
        Thread t = Thread.currentThread();
        // 调用getMap 方法获得当前线程中的本地变量ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        // 如果ThreadLocalMap 已存在,直接使用
        if (map != null)
            // 以当前的ThreadLocal 的实例作为key,存储于当前线程的
            // ThreadLocalMap 中,如果当前线程中定义了多个不同的ThreadLocal
            // 的实例,则它们会作为不同key 进行存储而不会互相干扰
            map.set(this, value);
        else
            // 如果ThreadLocalMap 不存在,则为当前线程创建一个新的
            createMap(t, value);
    }

    // 获取当前线程中以当前ThreadLocal 实例为key 的变量值
    public T get() {
        // 获取当前线程
        Thread t = Thread.currentThread();
        // 获取当前线程中的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            // 获取当前线程中以当前ThreadLocal 实例为key 的变量值
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T) e.value;
        }
        // 当map 不存在时,设置初始值
        return setInitialValue();
    }

    // 从当前线程中获取与之对应的ThreadLocalMap
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    // 创建当前线程中的ThreadLocalMap
    void createMap(Thread t, T firstValue) {
        // 调用构造函数生成当前线程中的ThreadLocalMap
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

    // ThreadLoaclMap 的定义
    static class ThreadLocalMap {
        //这里省略了许多代码
    }
}
  • ThreadLocalMap变量属于线程的内部属性,不同的线程拥有完全不同的ThreadLo-calMap变量。
  • 线程中的ThreadLocalMap变量的值是在ThreadLocal对象进行set或者get操作时创建的。
  • 在创建ThreadLocalMap之前,会首先检查当前线程中的ThreadLocalMap变量是否已经存在,如果不存在则创建一个;如果已经存在,则使用当前线程已创建的ThreadLo-calMap。
  • 使用当前线程的ThreadLocalMap的关键在于使用当前的ThreadLocal的实例作为key进行存储。

ThreadLocal模式至少从两个方面完成了数据访问隔离,即横向隔离和纵向隔离。

  • 纵向隔离——线程与线程之间的数据访问隔离。这一点由线程的数据结构保证。因为每个线程在进行对象访问时,访问的都是各个线程自己的ThreadLocalMap。
  • 横向隔离——同一个线程中,不同的Thread-Local实例操作的对象之间相互隔离。这一点由ThreadLocalMap在存储时采用当前ThreadLocal的实例作为key来保证。

深入比较ThreadLocal模式与synchronized关键字

  • ThreadLocal是一个Java类,通过对当前线程中的局部变量的操作来解决不同线程的变量访问的冲突问题。所以,ThreadLocal提供了线程安全的共享对象机制,每个线程都拥有其副本。
  • Java中的synchronized是一个保留字,它依靠JVM的锁机制来实现临界区的函数或者变量在访问中的原子性。在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。此时,被用作“锁机制”的变量是多个线程共享的。
  • 同步机制(synchronized关键字)采用了“以时间换空间”的方式,提供一份变量,让不同的线程排队访问。而ThreadLocal采用了“以空间换时间”的方式,为每一个线程都提供一份变量的副本,从而实现同时访问而互不影响。

要完成ThreadLocal模式,其中最关键的地方就是创建一个任何地方都可以访问到的ThreadLocal实例。而这一点,我们可以通过类变量来实现,这个用于承载类变量的类就被视作是一个共享环境。

public class Counter {
    // 新建一个静态的ThreadLocal 变量,并通过get 方法将其变为一个可访问的对象
    private static ThreadLocal<Integer> counterContext = new
            ThreadLocal<Integer>() {
                protected synchronized Integer initialValue() {
                    return 10;
                }
            };

    // 通过静态的get 方法访问ThreadLocal 中存储的值
    public static Integer get() {
        return counterContext.get();
    }

    // 通过静态的set 方法将变量值设置到ThreadLocal 中
    public static void set(Integer value) {
        counterContext.set(value);
    }

    // 封装业务逻辑,操作存储于ThreadLocal 中的变量
    public static Integer getNextCounter() {
        counterContext.set(counterContext.get() + 1);
        return counterContext.get();
    }
}
public class ThreadLocalTest extends Thread {
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println("Thread[" + Thread.currentThread().getName() + "],counter=" + Counter.getNextCounter());
        }
    }
}
public class Test {
    public static void main(String[] args) throws Exception {
        ThreadLocalTest testThread1 = new ThreadLocalTest();
        ThreadLocalTest testThread2 = new ThreadLocalTest();
        ThreadLocalTest testThread3 = new ThreadLocalTest();
        testThread1.start();
        testThread2.start();
        testThread3.start();
    }
}

我们来运行一下上面的代码,并看看输出结果:

Thread[Thread-2],counter=11
Thread[Thread-2],counter=12
Thread[Thread-2],counter=13
Thread[Thread-0],counter=11
Thread[Thread-0],counter=12
Thread[Thread-0],counter=13
Thread[Thread-1],counter=11
Thread[Thread-1],counter=12
Thread[Thread-1],counter=13

ThreadLocal模式最合适的使用场景:在同一个线程的不同开发层次中共享数据。

ThreadLocal模式的两个主要步骤:

  • 建立一个类,并在其中封装一个静态的ThreadLocal变量,使其成为一个共享数据环境。
  • 在类中实现访问静态ThreadLocal变量的静态方法(设值和取值)。

未完待续...

时间: 2024-10-11 07:11:12

TreadLocal模式的原理的相关文章

java多线程模式ThreadLocal原理简述及其使用详解

原创整理不易,转载请注明出处:java多线程模式ThreadLocal原理简述及其使用详解 代码下载地址:http://www.zuidaima.com/share/1781557457128448.htm ThreadLocal是为了使每个线程保存一份属于自己的数据. 先看一个使用ThreadLocal的实例. package com.zuidaima.aop.framework; import com.zuidaima.core.NamedThreadLocal; public abstra

MVC模式的原理

说说MVC模式的原理,Android SDK 中有哪些组件使用到了MVC模式,其基本原理是什么?[国内某著名软件外包公司 2010 年面试题] 答案:MVC 的基本原理就是通过Controller 连接View 和Model.也就是说,当View 中显示的数据变化时(如ListView要删除某个列表项),会通知Controller,而不是直接通知Model.这时Controller接到View的通知后,会在Model 中采取相应的动作(如删除数据库中的某条记录).如果模型的数据发生变化(如插入.

深入理解【代理模式】原理与技术

代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问. 23种常用的面向对象软件的设计模式之一. 代理模式分为静态代理.动态代理. 如何理解代理模式? 思考抽象问题最好的办法就是具体化! 比如我们需要为一个业务方法在执行前后记录日志,为了达到解耦的目的,我们可以再新建一个类并定义一个新的业务方法,该方法既可以调用原业务方法,又可以在调用前后进行日志处理,例如: CarProxy.class public void move() { System.out.println("日志开始记录..

分组密码_计数器(CTR)模式_原理及java实现

一.原理: CTR模式是一种通过将逐次累加的计数器进行加密来生成密钥流的流密码,在CTR模式中,每个分组对应一个逐次累加的计数器,并通过对计数器进行加密来生成密钥流.最终的密文分组是通过将计数器加密得到的比特序列与明文分组进行XOR而得到的. 二.原理图: 三.CRT模式的优点: 1.硬件效率高,同三种链接模式相比,CTR能够并行加密和解密. 2.软件效率高,可以充分利用其并行特性进行并行计算 3.由于加密解密过程不依赖明文和密文,因此可以做预处理以提高效率 4.可以随机访问某一明文或者密文分组

突破Java面试-Redis集群模式的原理

1 面试题 Redis集群模式的工作原理说一下?在集群模式下,key是如何寻址的?寻址都有哪些算法?了解一致性hash吗? 2 考点分析 Redis不断在发展-Redis cluster集群模式,可以做到在多台机器上,部署多个实例,每个实例存储一部分的数据,同时每个实例可以带上Redis从实例,自动确保说,如果Redis主实例挂了,会自动切换到redis从实例顶上来. 现在新版本,大家都是用Redis cluster的,也就是原生支持的集群模式,那么面试官肯定会就redis cluster对你来

请描述LVS的nat模式的原理

LVS-NAT:地址转换===收费站模式 virtual servervia network address translation(VS/NAT) LVS的nat模式类似于DNAT,但支持多目标转发.通过修改请求报文的目标地址为根据调度算法所挑选出的某RS的RIP来进行转发: 架构特性: (1)RS应该使用私有地址,即RIP应该为私有地址:各RS的网关必须指向DIP: (2)请求和响应报文都经由director转发:高负载场景中,dircetor可能成为瓶颈: (3)支持端口映射: (4)RS

EXC_BAD_ACCESS的本质详解以及僵尸模式调试原理

有时候,你会遇到由EXC_BAD_ACCESS造成的崩溃. 这篇文章会告诉你什么是EXC_BAD_ACCESS,以及它产生的原因.我还会提供一些EXC_BAD_ACCESS错误的解决方案. 1. 什么是 EXC_BAD_ACCESS? 一旦你理解EXC_BAD_ACCESS的本质,你就会更好地理解这个模糊的名词.这里有一个极为简单的解释,也有一个技术层面的解释.我们首先从简单的解释开始说起. 2. 简单的解释 不管什么时候当你遇到EXC_BAD_ACCESS这个错误,那就意味着你向一个已经释放的

HA主备路由模式的原理

HA是High Availability缩写,即高可用性 ,可防止网络中由于单个防火墙的设备故障或网络故障导致网络中断,保证网络服务的连续性和安全强度.目前,ha功能已经是防火墙内一个重要组成部分.        主备模式(Active-standby):在一个冗余组中,有两台防火墙,一台处于主状态.在这个状态下,防火墙响应ARP请求,并且转发网络流量:另一台处于备份状态,该防火墙不响应ARP请求,也不转发网络流量.主备之间同步状态信息,当主墙down机或网线故障时,进行主备切换.       

HDR 拍照模式的原理,实现及应用

HDR 拍照: (High Dynamic Range Imaging)高动态范围成像,是用来实现比普通数字图像技术更大曝光动态范围(即更大的明暗区别)的一组技术. 高动态范围成像的目的就是要正确地表示真实世界中从太阳光直射到最暗的阴影这样大的范围亮度.如今一般的数码相机和手机中都实现这样的拍照模式. 适合场景: 比較适合在阴暗变化明显的场景下使用,这样能使明处的景物不致过曝,而使得暗处的景物不致欠曝.譬如逆光环境下拍人物,能够将人物和环境都能拍清晰. 或者说能将处在暗处的景物拍摄出来的细节表现