线程安全的map_ThreadLocal | 线程本地存储
并发场景下,多个线程同时读写共享变量就有可能产生并发安全问题。反过来也可以说,不存在共享变量,就不会出现线程安全问题。Java中有两种常用的避免共享变量的方法,使用局部变量,以及使用 ThreadLocal。
局部变量存在于每个线程内部的调用栈中,多个线程之间互相访问不到对方的局部变量,这就叫做线程封闭。如下图所示,局部变量存在于线程各自的调用栈中,线程之间互不打扰。
采用局部变量的方案,的确避免了变量被多个线程共享,同时它也禁止同一个线程中不同方法共享这个变量。然而,单线程中不同的方法之间共享变量是不会导致线程安全问题的。
如果想让同一个线程,不同的方法共享变量就可以使用 ThreadLocal,Java 提供的线程本地存储方案。ThreadLocal 可以保证同一个变量,该线程中的方法看到的值是一样,不同线程之间却是隔离。
ThreadLocal 的使用方法
常规使用 ThreadLocal 的方式很简单,创建一个 ThreadLocal 对象,然后调用它的 set(value)
方法设置值,再调用 get()
方法获取这个 ThreadLocal 对象对应的value。
// 创建一个 ThreadLocal
ThreadLocal<String> tl = new ThreadLocal<>();
// set方法
tl.set("深页");
// get方法
tl.get();
ThreadLocal 类的注释中还带有为每个线程分配自增 id 的示例代码。withInitial()
方法会调用initialValue()
方法,为 ThreadLocal 设置 get()
的初始值。执行下面的代码,可以看到每个类都有自己的id,并且id的自增的。
public class ThreadId {// Integer类型的原子类,用来分配Id,保证其本身是线程安全的private static final AtomicInteger nextId = new AtomicInteger();// 创建一个 ThreadLocal 变零,并且为其赋值private static ThreadLocal <Integer> threadId = ThreadLocal.withInitial(() -> nextId.getAndIncrement());// 获取id,即从ThreadLocal中获取对应的值public static int getId() {return threadId.get();}public static void main(String[] args) {for (int i = 0; i < 100; i++) {new Thread(() ->System.out.println(Thread.currentThread().getName()+ ": " + ThreadId.getId())).start();}}
}
ThreadLocal还有一个经典的使用案例,就是将线程不安全的 SimpleDateFormat 类封装成线程安全的,原理其实和上面的例子是一样:
static class SafeDateFormat {static final ThreadLocal<DateFormat> tl = ThreadLocal.withInitial(()-> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));static DateFormat get() {return tl.get();}
}
ThreadLocal的底层原理
先来看 set()
方法:
- 首先获取当前线程,然后通过当前线程获取线程持有的局部变量 threadLocals
- 如果返回的 map 不是空的就设置值
- 如果返回的 map 是空的,就调用构造方法初始化 map 并为其设置值
public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);
}
再看 get()
方法:
public T get() {Thread t = Thread.currentThread();// getMap()返回当前线程的threadLocalsThreadLocalMap map = getMap(t);if (map != null) {// map存在返回当前ThreadLocal对应的value值ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}// map不存在就初始化return setInitialValue();
}
看过上面两个方法,可以看到它们除了涉及到 Thread 类,还涉及到了一个类 ThreadLocalMap。那么 Thread、ThreadLocal、ThreadLocalMap 之间是什么关系呢?
ThreadLocalMap 是 ThreadLocal 的静态内部类,ThreadLocalMap 的底层是一个 Entry[] table
数组,Entry 是 ThreadLocalMap 的静态内部类,以 ThreadLocal 作为 key,以设置的值作为 value,如下所示:
static class Entry extends WeakReference <ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}
}
Thread 持有一个 ThreadLocalMap 的引用 threadLocals
:
ThreadLocal.ThreadLocalMap threadLocals = null;
所以说,我们通过当前线程 Thread t
可以到 t
持有的 ThreadLocalMap,并且通过 ThreadLocal 对象返回其对应的 value。
ThreadLocal 内存泄露问题
使用 Thread.start()
方法是不会产生内存泄露的问题的,只有当我们在线程池中使用 ThreadLocal 才有可能产生内存泄露问题。
内存泄露的本质是长生命周期的对象,持有短生命周期对象。当短生命周期的对象使用结束之后,理应被垃圾回收器回收,但是它却被一个更长生命周期的对象引用。通过可达性分析算法,该短生命周期的对象被一个GC Root引用,理应被回收的它就无法被回收。
**那为什么在线程池中使用ThreadLocal就可能发生内存泄露的问题呢?**我们就从长生命周期的对象,持有短生命周期对象这个角度进行分析。
线程池作为一种池化资源技术,目的是避免线程的频繁创建和销毁。一般来说,线程池中的线程生命周期都很长,是和应用程序同生共死的。这就意味着,被 Thread 持有的 ThreadLocalMap 一直都不会被回收。
ThreadLocalMap 底层是一个 Entry 数组,Entry是<ThreadLocal,value>对结构。Entry 对 ThreadLocal 是弱引用(WeakReference),所以ThreadLocal 生命周期之后,是结束是可以被回收掉的。但是 Entry 对 value 强引用的,所以即便 Value 的生命周期结束了,Value 也是无法被回收的,从而导致内存泄露。
InheritableThreadLocal 与继承性
使用 ThreadLocal 还有这样一种需求,ThreadLocal 创建了线程变量 V,然后希望该线程创建的子线程也能访问到父线程的线程变量 V。
为此 Java 提供了 InheritableThreadLocal
来支持这种特性,InheritableThreadLocal 继承自 ThreadLocal,用法其实和 ThreadLocal 一样。
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
最后做一个小结,多线程同时读写共享变量就有可能产生并发问题。一种解决并发问题的思路就是避免变量被共享。与之对应的技术有线程隔离(局部变量),以及线程本地存储ThreadLocal
。
相比于使用局部变量,ThreadLocal 存储的变量可以供线程中的方法共享,单线程对共享变量的读写必定是线程安全的。
线程安全的map_ThreadLocal | 线程本地存储相关推荐
- Java中的线程本地存储
开发人员中鲜为人知的功能之一是线程本地存储. 这个想法很简单,并且在需要数据的情况下很有用. 如果我们有两个线程,则它们引用相同的全局变量,但我们希望它们具有彼此独立初始化的单独值. 大多数主要的编程 ...
- java线程本地存储_[并发并行]_[C/C++]_[使用线程本地存储Thread Local Storage(TLS)-win32和pthread比较]...
场景: 1. 需要统计某个线程的对象上创建的个数. 2. 当创建的堆空间需要根据线程需要创建和结束时销毁时. 3. 因为范围是线程只能看到自己的存储数据,所以不需要临界区或互斥量来维护自己的堆内存. ...
- linux 有线程本地存储 (tls)?,有没有办法确定Linux上的库使用的线程本地存储模型...
我自己遇到了这个错误,在调查时,我来了一个 mailing list post with this info: If you link a shared object containing IE-mo ...
- c语言 多个线程对同一变量执行memcpy_你可曾听过网络编程中应用线程本地存储?...
壹:你可曾听过线程本地存储? 1. 什么是线程本地存储? 线程本地存储:thread local storage(简称TLS).也叫线程特有存储:thread specific storage(简称T ...
- 聊聊Linux中的线程本地存储(1)——什么是TLS
从本篇开始进入另一个话题:线程本地存储(Thread Local Storage),在介绍这个概念前先说说变量和多线程的相关知识. 多线程下的变量模型 在单线程模型下,变量定义有两个维度,那就是在何处 ...
- Disruptor本地线程队列_实现线程间通信---线程间通信工作笔记001
Disruptor本地线程队列_实现线程间通信---线程间通信工作笔记001 看到同事用这个东西了,这个挺好用的说是,可以实现,本地线程间的通信,好像在c++和java中都可以用 现在没时间研究啊,暂 ...
- java线程 同步与异步 线程池
1)多线程并发时,多个线程同时请求同一个资源,必然导致此资源的数据不安全,A线程修改了B线 程的处理的数据,而B线程又修改了A线程处理的数理.显然这是由于全局资源造成的,有时为了解 决此问题,优先考虑 ...
- 【Java 并发编程】线程简介 ( 并发类型 | 线程状态 | CPU 数据缓存 )
文章目录 一.并发类型 二.线程状态 三.CPU 数据缓存 一.并发类型 并发类型 : Thread Runnable Future ThreadPool 其中 Runnable , ThreadPo ...
- 【Android应用开发】 Android 崩溃日志 本地存储 与 远程保存
示例代码下载 : http://download.csdn.net/detail/han1202012/8638801; 一. 崩溃日志本地存储 1. 保存原理解析 崩溃信息本地保存步骤 : -- 1 ...
最新文章
- ConcurrentHashMap之实现细节
- C# Thread开启线程几种方式
- 印尼发生洪灾和山体滑坡 致多人死亡数千人撤离
- 串口开发,数据类型转换——字符串转 byte[],byte[]转二进制,二进制转十进制转byte[],byte[]转十进制,byte[]拼接,校验
- java的代理Proxy.newProxyInstance
- Tcl Tutorial 笔记4 ·if
- Linux 与 Windows 计算文件夹大小
- C陷阱与缺陷(二)语义“陷阱”、连接
- java实现一码多扫支付_详解JAVA后端实现统一扫码支付:微信篇
- aspcms标签大全
- 中文 APB Artist Sessions Presents- SHAUN BARRETT
- Unity 3d 摄像头
- php 文字转unicode,php汉字如何转unicode
- JAVA渣渣感悟——三目运算符(三元运算符)的注意事项
- 社区发现算法原理与louvain源码解析
- 点云处理--点云平移和旋转
- public、private、protected的区别
- kafka安装(windows版)
- 信息奥赛一本通1208:2的幂次方表示
- vue-barcode生成条形码