Java多线程_ThreadLocal
用法:
ThreadLocal用于保存某个线程共享变量:对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。
- ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。
- ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。
- ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。
- ThreadLocal.initialValue: ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值。
public class MyThreadLocal
{
private static final ThreadLocal<Object> threadLocal = new ThreadLocal<Object>(){
/**
* ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值
*/
@Override
protected Object initialValue()
{
System.out.println("调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!");
return null;
}
}; public static void main(String[] args)
{
new Thread(new MyIntegerTask("IntegerTask1")).start();
new Thread(new MyStringTask("StringTask1")).start();
new Thread(new MyIntegerTask("IntegerTask2")).start();
new Thread(new MyStringTask("StringTask2")).start();
} public static class MyIntegerTask implements Runnable
{
private String name; MyIntegerTask(String name)
{
this.name = name;
} @Override
public void run()
{
for(int i = 0; i < 5; i++)
{
// ThreadLocal.get方法获取线程变量
if(null == MyThreadLocal.threadLocal.get())
{
// ThreadLocal.et方法设置线程变量
MyThreadLocal.threadLocal.set(0);
System.out.println("线程" + name + ": 0");
}
else
{
int num = (Integer)MyThreadLocal.threadLocal.get();
MyThreadLocal.threadLocal.set(num + 1);
System.out.println("线程" + name + ": " + MyThreadLocal.threadLocal.get());
if(i == 3)
{
MyThreadLocal.threadLocal.remove();
}
}
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
} } public static class MyStringTask implements Runnable
{
private String name; MyStringTask(String name)
{
this.name = name;
} @Override
public void run()
{
for(int i = 0; i < 5; i++)
{
if(null == MyThreadLocal.threadLocal.get())
{
MyThreadLocal.threadLocal.set("a");
System.out.println("线程" + name + ": a");
}
else
{
String str = (String)MyThreadLocal.threadLocal.get();
MyThreadLocal.threadLocal.set(str + "a");
System.out.println("线程" + name + ": " + MyThreadLocal.threadLocal.get());
}
try
{
Thread.sleep(800);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
}
结果:

ThreadLocal的核心机制:
- 每个Thread线程内部都有一个Map。
- Map里面存储线程本地对象(key)和线程的变量副本(value)
- Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。
所以对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。
ThreadLocalMap
ThreadLocalMap是ThreadLocal的内部类,没有实现Map接口,用独立的方式实现了Map的功能,其内部的Entry也独立实现。在ThreadLocalMap中,也是用Entry来保存K-V结构数据的。但是Entry中key只能是ThreadLocal对象,这点被Entry的构造方法已经限定死了。
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
Entry继承自WeakReference(弱引用,生命周期只能存活到下次GC前),但只有Key是弱引用类型的,Value并非弱引用。
Hash冲突怎么解决?
和HashMap的最大的不同在于,ThreadLocalMap结构非常简单,没有next引用,也就是说ThreadLocalMap中解决Hash冲突的方式并非链表的方式,而是采用线性探测的方式,所谓线性探测,就是根据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。
ThreadLocalMap解决Hash冲突的方式就是简单的步长加1或减1,寻找下一个相邻的位置。
/**
* Increment i modulo len.
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
} /**
* Decrement i modulo len.
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
显然ThreadLocalMap采用线性探测的方式解决Hash冲突的效率很低,如果有大量不同的ThreadLocal对象放入map中时发送冲突,或者发生二次冲突,则效率很低。
所以这里引出的良好建议是:每个线程只存一个变量,这样的话所有的线程存放到map中的Key都是相同的ThreadLocal,如果一个线程要保存多个变量,就需要创建多个ThreadLocal,多个ThreadLocal放入Map中时会极大的增加Hash冲突的可能。
ThreadLocalMap的问题
由于ThreadLocalMap的key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。
如何避免泄漏?
既然Key是弱引用,那么我们要做的事,就是在调用ThreadLocal的get()、set()方法时完成后再调用remove方法,将Entry节点和Map的引用关系移除,这样整个Entry对象在GC Roots分析后就变成不可达了,下次GC的时候就可以被回收。
如果使用ThreadLocal的set方法之后,没有显示的调用remove方法,就有可能发生内存泄露,所以养成良好的编程习惯十分重要,使用完ThreadLocal之后,记得调用remove方法。
总结
- 每个ThreadLocal只能保存一个变量副本,如果想要一个线程能够保存多个副本,就需要创建多个ThreadLocal。
- ThreadLocal内部的ThreadLocalMap键为弱引用,会有内存泄漏的风险。
- 适用于无状态,副本变量独立后不影响业务逻辑的高并发场景。如果如果业务逻辑强依赖于副本变量,则不适合用ThreadLocal解决,需要另寻解决方案。
Java多线程_ThreadLocal的更多相关文章
- 40个Java多线程问题总结
前言 Java多线程分类中写了21篇多线程的文章,21篇文章的内容很多,个人认为,学习,内容越多.越杂的知识,越需要进行深刻的总结,这样才能记忆深刻,将知识变成自己的.这篇文章主要是对多线程的问题进行 ...
- Java多线程基础知识篇
这篇是Java多线程基本用法的一个总结. 本篇文章会从一下几个方面来说明Java多线程的基本用法: 如何使用多线程 如何得到多线程的一些信息 如何停止线程 如何暂停线程 线程的一些其他用法 所有的代码 ...
- Java多线程系列--“JUC锁”03之 公平锁(一)
概要 本章对“公平锁”的获取锁机制进行介绍(本文的公平锁指的是互斥锁的公平锁),内容包括:基本概念ReentrantLock数据结构参考代码获取公平锁(基于JDK1.7.0_40)一. tryAcqu ...
- Java多线程系列--“JUC锁”04之 公平锁(二)
概要 前面一章,我们学习了“公平锁”获取锁的详细流程:这里,我们再来看看“公平锁”释放锁的过程.内容包括:参考代码释放公平锁(基于JDK1.7.0_40) “公平锁”的获取过程请参考“Java多线程系 ...
- Java多线程--让主线程等待子线程执行完毕
使用Java多线程编程时经常遇到主线程需要等待子线程执行完成以后才能继续执行,那么接下来介绍一种简单的方式使主线程等待. java.util.concurrent.CountDownLatch 使用c ...
- Java多线程 2 线程的生命周期和状态控制
一.线程的生命周期 线程状态转换图: 1.新建状态 用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态.处于新生状态的线程有自己的内存空间,通过调用start方法进入就 ...
- java 多线程 1 线程 进程
Java多线程(一).多线程的基本概念和使用 2012-09-10 16:06 5108人阅读 评论(0) 收藏 举报 分类: javaSE综合知识点(14) 版权声明:本文为博主原创文章,未经博 ...
- 一起阅读《Java多线程编程核心技术》
目录 第一章 Java多线程技能 (待续...)
- 第一章 Java多线程技能
1.初步了解"进程"."线程"."多线程" 说到多线程,大多都会联系到"进程"和"线程".那么这两者 ...
随机推荐
- Linux系统安装Samba共享服务器详解及安装配置
一.简介 Samba服务程序是一款基于SMB协议并由服务端和客户端组成的开源文件共享软件,实现了Linux和windows系统间的文件共享.SMB(Server Messages Block,信息服务 ...
- VulnHub靶场学习_HA: Pandavas
HA: Pandavas Vulnhub靶场 下载地址:https://www.vulnhub.com/entry/ha-pandavas,487/ 背景: Pandavas are the warr ...
- MacOS下SpringBoot基础学习
学于黑马和传智播客联合做的教学项目 感谢 黑马官网 传智播客官网 微信搜索"艺术行者",关注并回复关键词"springboot"获取视频和教程资料! b站在线视 ...
- 最优化算法【牛顿法、拟牛顿法、BFGS算法】
一.牛顿法 对于优化函数\(f(x)\),在\(x_0\)处泰勒展开, \[f(x)=f(x_0)+f^{'}(x_0)(x-x_0)+o(\Delta x) \] 去其线性部分,忽略高阶无穷小,令\ ...
- 036_go语言中的原子计数器
代码演示 package main import ( "fmt" "runtime" "sync/atomic" "time&qu ...
- 团队项目-记账App
一.团队成员介绍 队长: 向瑜 博客园地址: https://www.cnblogs.com/xiangyu721/ 沟通能力较强,善于总结,能够正确分配团队任务.其次,有耐心学习新事物,发现新问题 ...
- 入门python有什么好的书籍推荐?纯干货推荐,你值得一看 python基础,爬虫,数据分析
Python入门书籍不用看太多,看一本就够.重要的是你要学习Python的哪个方向,或者说你对什么方向感兴趣,因为Python这门语言的应用领域比较广泛,比如说可以用来做数据分析.机器学习,也可以用来 ...
- .NET和.NET Core Web APi FormData多文件上传对比
前言 最近因维护.NET和.NET Core项目用到文件上传功能,虽说也做过,但是没做过什么对比,借此将二者利用Ajax通过FormData上传文件做一个总结,通过视图提交表单太简单,这里不做阐述,希 ...
- 创建shell脚本文件
简单来说脚本就是将需要执行的命令保存到文本中,按照顺序(由上往下执行),它是解释型的,不需要 编译 脚本格式 #!/bin/bash或者#!/bin/env bash开头 第一个shell脚本:hel ...
- 【NBA 可视化】使用Pyecharts实现湖人19-20赛季投篮数据可视化~
前言 先来口号 「湖人总冠军」~