ThreadLocal线程局部变量的使用
ThreadLocal: 线程局部变量
一)、ThreadLocal的引入
用途:是解决多线程间并发访问的方案,不是解决数据共享的方案。
特点:每个线程提供变量的独立副本,所有的线程使用同一个ThreadLocal,
通过ThreadLocal来创建自己的独立副本。
好处: 解决多个线程使用同一个引用可能出现的问题。
例如:使用SimpleDateFormat进行日期装换
声明 public static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
将日期转换对象声明为所有线程共有。
SimpleDateFormat内部维护一个Calendar对象,该对象用于存储 sdf.parse(dateStr)或sdf.format(date)的信息,通过clear()来清除设置信息,通过getTime()来获取日期信息,因为sdf为所有的线程共有,因此所有的线
均指向同一个Calendar引用,当线程A执行完Calendar.clear(),后线程的执权到达线程B中,线程B也执行了Calendar.claer(),之后执行Calendar.getTime()得到装换的日期类,此时线程的执行权到达线程A中
此时线程A打印的是线程B的时间,如果线程B执行完Calendar.claer()后执行权被线程A抢夺,就会出现java.lang.NumberFormatException异常。
解决:每一个线程使用一个自己的SimpleDateFomat副本,每一个线程有自己的
Calendar
二)、使用ThreadLocal创建独立副本的步骤
1).创建ThreadLocal
//T: 创建独立副本的变量类型。
ThreadLocal<t> threadLocal = new ThreadLocal<T>();
public ThreadLocal() {
}
2).通过set()设置当前线程的独立副本
threadLocal.set(T t)
public void set(T value) {
//1.获取当前的线程对象
Thread t = Thread.currentThread();
//2.获取当前线程对象的map集合
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
// threaLocals是一个ThreadLocalMap对象。ThreadLocal.ThreadLocalMap threadLocals
return t.threadLocals;
}
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
3.通过get()获取当前线程的独立副本
threadLocal.get()
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();
}
三)、使用普通方法实现多线程日期转换
package com;
/**
* author:chenxuebing
* Date:2019/11/1
* Time:13:51
*/
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 不使用ThreadLocal进行时间装换时出现的问题
* 多个线程共同使用一个sdf对象,SimpleDataFormat内部维护着一个Caledar对象
* Caledar对象存储着parse(dateStr)或format(date)中dateStr、date的信息
* Caledar执行两个操作
* claer(); 清楚维护的date或dateStr的信息
* getTime(); 获取date
*
* 当所有的线程共用一个SimpleDateFormat对象时,使用同一个Calendar引用
* 此时,出现问题:
* 线程A调用parse(), 并没有getTime(),此时轮到了线程B执行parse(),线程B执行完后也clear(),线程A, getTime()
* 最后得到的是线程B的时间
*
* ThreadLocal: 解决了static修饰的共享变量内部操作不一致的问题。
*/
public class NormalParseDate implements Runnable{
/**
* 多个线程共享的变量
*/
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd HH:mm:ss");
int i = 0;
public NormalParseDate(int i) {
this.i = i;
}
/**
* 日期装换方法
*/
@Override
public void run() {
try {
//所有的线程使用同一个sdf
Date date = sdf.parse("2017-08-02 11:22:"+ i % 60);
System.out.println(i+" : "+date);
} catch (ParseException e) {
e.printStackTrace();
}
}
/**
* 创建线程
*/
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(10);
for(int i = 0; i < 100; i++){
executor.execute(new NormalParseDate(i));
}
}
}
结果:
68 : Mon Jan 02 11:22:08 CST 2017
Exception in thread "pool-1-thread-2" java.lang.NumberFormatException: multiple points
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
结论:因为多个线程使用同一个SimpldeDateFormat对象,指向同一个Calendar引
用,在多线程调用时会出现数据交替问题。
四)、使用ThreadLocal实现多线程日期转换:
/**
* 使用ThreadLocal线程局部变量来解决线程间共享变量内部操作不一致问题
*/
public class ThreadLocalParseData implements Runnable{
//线程共用一个threadLocal对象
private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<>();
int i = 0;
public ThreadLocalParseData(int i) {
this.i = i;
}
@Override
public void run() {
//判断当前线程是否有SimpleDateFormat对象,有则使用,没有则创建
if(threadLocal.get() == null){
//如果没有就给当前线程创建一个, 每一个线程都有一个自己的SimpleDateFormat对象
threadLocal.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}
//有,则直接使用
try {
Date date = threadLocal.get().parse("2017-2-21 14:29:" + i % 60);
System.out.println(i+":"+date);
} catch (ParseException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//创建线程对象,每个线程都有一个SimpleDateFormat对象
ExecutorService executor = Executors.newFixedThreadPool(10);
for(int i = 0; i < 100; i++){
executor.execute(new ThreadLocalParseData(i));
}
}
}
ThreadLocal线程局部变量的使用的更多相关文章
- Java并发编程原理与实战二十五:ThreadLocal线程局部变量的使用和原理
1.什么是ThreadLocal ThreadLocal顾名思义是线程局部变量.这种变量和普通的变量不同,这种变量在每个线程中通过get和set方法访问, 每个线程有自己独立的变量副本.线程局部变量不 ...
- 线程局部变量ThreadLocal的原理及使用范围_1
线程局部变量ThreadLocal的原理及使用范围 使用原理 每个Thread中都有一个ThreadLocalMap成员, 该成员是ThreadLocal的内部类ThreadLocalMap类型.每使 ...
- 线程局部变量ThreadLocal实现原理
ThreadLocal,即线程局部变量,用来为每一个使用它的线程维护一个独立的变量副本.这种变量只在线程的生命周期内有效.并且与锁机制那种以时间换取空间的做法不同,ThreadLocal没有任何锁机制 ...
- ThreadLocal 线程本地变量 及 源码分析
■ ThreadLocal 定义 ThreadLocal通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量 ...
- ThreadLocal(线程绑定)
为保证在DAO层里的操作都在同一事务里,我们曾使用以参数的形式将Connection向下传递的方式,而ThreadLocal来创建Connection连接,避免了一直以参数的形式将Connection ...
- java线程——线程局部变量
一,线程局部变量ThreadLocal的作用 用于实现线程内部的数据共享,既对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,在另一个线程访问的时候,访问的由是另一份数据. 每个线程调用 ...
- Java基础教程——线程局部变量
线程局部变量 ThreadLocal,线程局部变量,不提供锁,不做线程共享,而是为每个线程提供变量的独立副本. import java.util.concurrent.*; public class ...
- 【java】ThreadLocal线程变量的实现原理和使用场景
一.ThreadLocal线程变量的实现原理 1.ThreadLocal核心方法有这个几个 get().set(value).remove() 2.实现原理 ThreadLocal在每个线程都会创建一 ...
- [Python]threading local 线程局部变量小測试
概念 有个概念叫做线程局部变量.一般我们对多线程中的全局变量都会加锁处理,这样的变量是共享变量,每一个线程都能够读写变量,为了保持同步我们会做枷锁处理.可是有些变量初始化以后.我们仅仅想让他们在每一个 ...
随机推荐
- python学习-多线程并发
1.线程与进程 通俗解释: 对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进 ...
- (一)如何理解java面向对象编程
哲学中,事物总是螺旋式上升,波浪式前进.因而编程也逐渐向人类更容易理解的方向前进,多年来人们苦苦追求的编程境界 : 高扩展性(extensibility),高复用性(reuseable).java语言 ...
- 函数基础(一)(day10整理)
目录 昨日内容 文件的基本应用 什么是文件 操作文件的流程 打开文件的三种模式 文件打开的两种方式 绝对路径和相对路径 绝对路径 相对路径 with管理文件上下文 文件的高级应用 新的打开文件的模式 ...
- Java11新特性 - Epsilon GC和ZGC
Java11中新增了两个GC,Epsilon GC和ZGC. Epsilon垃圾收集器 A NoOp Garbage Collector 没有操作的垃圾收集器 JDK上对这个特性的描述是:开发一个处理 ...
- Java基础(二十)集合(2)Collection接口
1.Collection接口通常不被直接使用.但是Collection接口定义了一些通用的方法,通过这些方法可以实现对集合的基本操作,因为List接口和Set接口都实现了Collection接口,所以 ...
- vue事件
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- vue 踩坑--项目名称不能与插件名称一样
今天webpack搭建,命令行一直报错 百度说是项目名称为:vue npm 拒绝安装 后来才发现是 package.json的name名称也设成了vue 所以 修改一下就可以了.
- shell 脚本中的注释详解
上次写了shell脚本的注释,没想到那么多人的需要,也存在不少不足.这次做个补充吧. 单行注释: 单行注释就比较简单了,直接在行最前端加上符号 # 即可.具体用法如下所示: # this is com ...
- windows服务参考
dll文件 aaclient.dll 何时何地都可以访问客户端 accessibilitycpl.dll 轻松访问控制面板 acledit.dll 访问控制列表编辑器 aclui.dll 安全描述符编 ...
- 欧拉路&&欧拉回路
T1是欧拉路板子,但我不会,直接爆炸.. 这玩意就是个dfs,但我以前一直以为欧拉路只能$O(nm)$求 今天才知道可以$O(n+m)$ 欧拉路判定: 无向:起点终点为奇度点,其余偶度 有向:起点终点 ...