ThreadLocal设计原理

1. ThreadLocal

1.1 简介

ThreadLocal是线程内部的数据存储类,通过它可以指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取数据。

它能够满足以下需求:

  • 同一个变量在不同的线程中需要有不同的副本
  • 经常应用于static方法,无法在线程创建的时候赋值

1.2 应用场景

  • 数据库连接池
  • Hibernate的session
  • 其他线程不安全的类在多线程中不加锁使用

1.3 应用示例

时间解析类SimpleDateFormat是线程不安全的典型例子,因为同一时刻下只能支持一个解析表达式,多线程环境下使用只有两种选择:

  • 加锁访问
  • 通过ThreadLocal,让线程持有一个线程私有的SimpleDateFormat对象

    private static final ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<>();

    static String format(Date date, String pattern) {
        SimpleDateFormat simpleDateFormat = threadLocal.get();
        if (simpleDateFormat == null) {
            simpleDateFormat = new SimpleDateFormat();
            threadLocal.set(simpleDateFormat);
        }
        simpleDateFormat.applyPattern(pattern);
        return simpleDateFormat.format(date);
    }

2. 设计原理

接下来我们尝试着自己设计一个方案,让线程随时随地可以拥有自己的线程私有变量。通过设计方案的演变来帮助理解JDK的方案。

线程私有变量有两个保存地方可以选择:

  • 线程内部
  • 外部集合

2.1 保存在线程内

2.1.1 定义

最先想到的办法,也是最直白的,是通过Map来持有多个线程私有变量。

public class ThreadLocalInside extends Thread {
    WeakHashMap<Object, Object> threadLocals = new WeakHashMap<>();
}

2.1.2 使用样例

自己灵活的设置key来区分不同的线程私有变量。

    static String format(Date date, String pattern) {
        ThreadLocalInside thread = (ThreadLocalInside) Thread.currentThread();
        Class clazz = SimpleDateFormat.class;
        SimpleDateFormat simpleDateFormat = (SimpleDateFormat) thread.threadLocals.get(clazz);
        if (simpleDateFormat == null) {
            simpleDateFormat = new SimpleDateFormat();
            thread.threadLocals.put(clazz, simpleDateFormat);
        }
        simpleDateFormat.applyPattern(pattern);
        return simpleDateFormat.format(date);
    }

2.1.3 优点

  • 线程安全。不会有多线程访问的问题。

2.1.4 缺点

  • 使用不当容易内存占用过大。私有变量存活时间与线程一样长,无法利用上垃圾收集器。
  • 耦合。新增的API需要耦合在线程类中。
  • 管理不方便。如果要移除某个线程私有变量在所有线程中的副本,十分困难。

2.2 保存在外部集合

2.2.1 代码实现

public class ThreadLocalOutside extends WeakHashMap<Thread, HashMap<Object, Object>> {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    @Override
    public HashMap<Object, Object> put(Thread key, HashMap<Object, Object> value) {
        lock.writeLock().lock();
        try {
            return super.put(key, value);
        } finally {
            lock.writeLock().unlock();
        }
    }

    public HashMap<Object, Object> get(Thread key) {
        lock.readLock().lock();
        try {
            return super.get(key);
        } finally {
            lock.readLock().unlock();
        }
    }
}

2.2.2 使用样例

    private static final ThreadLocalOutside THREAD_LOCAL_MANAGER = new ThreadLocalOutside();

    static String format(Date date, String pattern) {
        Thread thread = Thread.currentThread();
        HashMap<Object, Object> threadLocalVariable = THREAD_LOCAL_MANAGER.get(thread);
        Class clazz = SimpleDateFormat.class;
        SimpleDateFormat simpleDateFormat = (SimpleDateFormat) threadLocalVariable.get(clazz);
        if (simpleDateFormat == null) {
            simpleDateFormat = new SimpleDateFormat();
            threadLocalVariable.put(clazz, simpleDateFormat);
        }
        simpleDateFormat.applyPattern(pattern);
        return simpleDateFormat.format(date);
    }

2.2.3 优点

  • 解耦。使用单独外部类来管理线程私有变量,不必加入线程api中。
  • 管理方便。可以轻松移除所有线程中的某个私有变量。

2.2.4 缺点

  • 性能问题。需要加锁避免线程不安全问题。
  • 使用不当容易内存占用过大。私有变量存活时间与线程一样长,无法利用上垃圾收集器。

3. JDK设计,内外结合

JDK的ThreadLocal包含了上面两种设计思想,即保存在线程内,却通过外部类提供API来操作。

新引入了一个设计:使用ThreadLocal对象作为私有变量集合的Key,每一个ThreadLocal对象代表一个种类的线程私有变量。这个设计解决了线程私有变量管理的痛点:

  • 当你需要有多少种线程私有变量,就创建多少个ThreadLocal对象,让线程私有变量管理起来比较方便。
  • 想让某个线程私有变量在所有线程中移除时,不再引用ThreadLocal对象即可,其他交给垃圾收集器。

3.1 代码实现

为了不让读者的注意力被弱引用散列表的实现细节分散,这里使用WeakHashMap代替ThreadLocal内部的ThreadLocalMap。

3.1.1 弱引用集合

简单介绍以下这两个集合功能:

  • 如果集合中的Key不再被其他对象引用,垃圾收集器会回收掉这个Key对象。
  • 调用put()、get()等方法时,会顺便删除散列表里的元素,条件是Key对象已经被垃圾回收。
  • 散列冲突时,WeakHashMap使用链表法,ThreadLocalMap使用开放定址法解决冲突(使用数组来节约内存),优点是实现简单。

3.1.2 山寨版JDK实现

public class Thread2 extends Thread {
    //ThreadLocal作为key,一个ThreadLocal代表一种线程私有变量
    WeakHashMap<ThreadLocal2, Object> threadLocals;
}

class ThreadLocal2<T> {
    //模板方法
    protected T initialValue() {
        return null;
    }

    public T get() {
        Thread2 thread = (Thread2) Thread.currentThread();
        WeakHashMap<ThreadLocal2, Object> threadLocalMap = thread.threadLocals;
        if (threadLocalMap != null) {
            Object value = threadLocalMap.get(this);
            if (value != null) {
                return (T) value;
            }
        }
        T value = initialValue();
        if (threadLocalMap != null) {
            threadLocalMap.put(this, value);
        } else {
            //懒加载,大部分线程不使用threadLocal,因此能节约不少内存
            thread.threadLocals = new WeakHashMap<>();
            thread.threadLocals.put(this, value);
        }
        return value;
    }

    public void set(T object) {
        Thread2 thread = (Thread2) Thread.currentThread();
        WeakHashMap<ThreadLocal2, Object> threadLocalMap = thread.threadLocals;
        if (threadLocalMap != null) {
            threadLocalMap.put(this, object);
        } else {
            //懒加载,大部分线程不使用threadLocal,因此能节约不少内存
            thread.threadLocals = new WeakHashMap<>();
            thread.threadLocals.put(this, initialValue());
        }
    }
}

3.2 使用样例

使用起来几乎和JDK实现的ThreadLocal一模一样呢

    private static final ThreadLocal2<SimpleDateFormat> threadLocal = new ThreadLocal2<>() ;

    static String format(Date date, String pattern) {
        SimpleDateFormat simpleDateFormat = threadLocal.get();
        if (simpleDateFormat == null) {
            simpleDateFormat = new SimpleDateFormat();
            threadLocal.set(simpleDateFormat);
        }
        simpleDateFormat.applyPattern(pattern);
        return simpleDateFormat.format(date);
    }

3.3 优点

  • 高性能:线程私有变量保存在线程内的集合中,不需要加锁。
  • 解耦:外部对象提供API。
  • 利用JDK垃圾回收机制:外部对象作为线程内散列表的Key,但是用弱引用持有Key,当外部对象不再被其他对象引用且已经被垃圾收集器回收后,会顺便删除散列表里的元素,条件是Key对象已经被垃圾回收。
  • 管理方便。可以轻松移除所有线程中的某个私有变量,只需要删除对应threadLocal的引用即可。

简直是去其糟粕取其精华。

3.4 缺点

  • 使用不当容易内存占用过大。私有变量存活时间与线程一样长。

原文地址:https://www.cnblogs.com/datartvinci/p/11069913.html

时间: 2024-07-31 19:21:58

ThreadLocal设计原理的相关文章

kafka入门:简介、使用场景、设计原理、主要配置及集群搭建(转)

问题导读: 1.zookeeper在kafka的作用是什么? 2.kafka中几乎不允许对消息进行"随机读写"的原因是什么? 3.kafka集群consumer和producer状态信息是如何保存的? 4.partitions设计的目的的根本原因是什么? 一.入门 1.简介 Kafka is a distributed,partitioned,replicated commit logservice.它提供了类似于JMS的特性,但是在设计实现上完全不同,此外它并不是JMS规范的实现.k

Atitit.ioc&#160;动态配置文件guice&#160;设计原理

Atitit.ioc 动态配置文件guice 设计原理 1. Bat启动时注入配置文件1 2. ioc调用1 3. Ioc 分发器 配合 apche  MethodUtils.invokeStaticMethod2 1. Bat启动时注入配置文件 SET JAVA_HOME=C:\Program Files\Java\jdk1.8.0_71 set  RESIN-HOME=c:\resin-4.0.22 set classpath=%classpath%;%RESIN-HOME%\lib\jas

BigPipe设计原理

高性能页面加载技术--BigPipe设计原理及Java简单实现 1.技术背景 动态web网站的历史可以追溯到万维网初期,相比于静态网站,动态网站提供了强大的可交互功能.经过几十年的发展,动态网站在互动性和页面显示效果上有了很大的提升,但是对于网站动态网站的整体页面加载架构没有做太大的改变.对于用户而言,页面的加载速度极大的影响着用户体验感.与静态网站不同,除了页面的传输加载时间外,动态网站还需考虑服务端数据的处理时间.像facebook这样大型的用户社交网站,必须考虑用户访问速度问题, 传统we

Atitit.异常的设计原理与&#160;策略处理&#160;java&#160;最佳实践&#160;p93

Atitit.异常的设计原理与 策略处理 java 最佳实践 p93 1 异常方面的使用准则,答案是::2 1.1 普通项目优先使用异常取代返回值,如果开发类库方面的项目,最好异常机制与返回值都提供,由调用者决定使用哪种方式..2 1.2 优先把异常抛出到上层处理..异常本身就是为了方便把异常情况抛出到上层处理..2 1.3 对于 HYPERLINK \l _Toc6222 方法调用结果异常情况返回策略,最终会有四种策略状况,2 1.4 返回null  还是异常??2 2 异常的由来与设计3 2

Atitit ati&#160;licenseService &#160;&#160;&#160;设计原理

Atitit ati licenseService    设计原理 C:\0workspace\AtiPlatf\src_atibrow\com\attilax\license\LicenseX.java V1 更具时间超是 V2   按照时间慢的百分率.. V3 草案.. Laicense file ,hto sh aes time.. Invoke System.out.println( licenseX.isCanUse_byUsePercent("2016-05-01") );

以属性为核心驱动的 全领域通用架构设计原理 (简称:属性架构原理)

以属性为核心驱动的全领域通用架构设计原理 (简称:属性架构原理) 联系方式:13547930387 Email:[email protected] 一.个人声明 我,参加工作也有5年多了,是一名普通的不能在普通的程序员,一直在使用公司自己的产品进行开发,因此技术比较菜,此设计完全是按照自己天真的想法而设计的,如果有不合理或很搞笑的地方,请轻拍,由衷的希望大家能提出宝贵的意见: 根据此设计原理我也做了一个简单的(demo)架构来支撑和验证此理论的可行性,由于技术功底不太好,有不合理之处请大家谅解,

深入理解kafka设计原理

最近开研究kafka,下面分享一下kafka的设计原理.kafka的设计初衷是希望作为一个统一的信息收集平台,能够实时的收集反馈信息,并需要能够支撑较大的数据量,且具备良好的容错能力. 1.持久性 kafka使用文件存储消息,这就直接决定kafka在性能上严重依赖文件系统的本身特性.且无论任何OS下,对文件系统本身的优化几乎没有可能.文件缓存/直接内存映射等是常用的手段.因为kafka是对日志文件进行append操作,因此磁盘检索的开支是较小的;同时为了减少磁盘写入的次数,broker会将消息暂

BeanFactory容器的设计原理

XmlBeanFactory设计的类继承关系 1.BeanFactory接口提供了使用IoC容器的规范.在这个基础上,Spring还提供了符合这个IoC容器接口的一系列容器的实现供开发人员使用. 2.我们以XmlBeanFactory的实现为例来说明简单IoC容器的设计原理. 3.可以看到,作为一个简单IoC容器系列最底层实现的XmlBeanFactory,与我们在Spring应用中用到的上下文相比,有一个非常明显的特点:它只提供最基本的IoC容器的功能. 4.理解这一点有助于我们理解Appli

Scala函数式编程设计原理 第一课 编程范式(Programming Paradigms)

我使用Scala有一两年的时间了,这门语言仿佛有一种魔力,让人用过就不想放手.Scala给我的整个程序生涯带来了非常深刻的影响,让我学会了函数式编程,让我知道了世界上居然还有这么一种优雅.高效.强大的语言. Scala在国外已经非常流行,但是不知为何,在国内总是不温不火,在此,我特别想为Scala这门语言在国内的发展做一些事情.不才不敢谈Scala的编程经验,因为要成为Scala大神还有很长的路要走,只好翻译一份Scala视频教程以飨读者,大家发现有误的地方,请多多批评指教. 这个视频的作者是S