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线程局部变量的使用的更多相关文章

  1. Java并发编程原理与实战二十五:ThreadLocal线程局部变量的使用和原理

    1.什么是ThreadLocal ThreadLocal顾名思义是线程局部变量.这种变量和普通的变量不同,这种变量在每个线程中通过get和set方法访问, 每个线程有自己独立的变量副本.线程局部变量不 ...

  2. 线程局部变量ThreadLocal的原理及使用范围_1

    线程局部变量ThreadLocal的原理及使用范围 使用原理 每个Thread中都有一个ThreadLocalMap成员, 该成员是ThreadLocal的内部类ThreadLocalMap类型.每使 ...

  3. 线程局部变量ThreadLocal实现原理

    ThreadLocal,即线程局部变量,用来为每一个使用它的线程维护一个独立的变量副本.这种变量只在线程的生命周期内有效.并且与锁机制那种以时间换取空间的做法不同,ThreadLocal没有任何锁机制 ...

  4. ThreadLocal 线程本地变量 及 源码分析

    ■ ThreadLocal 定义 ThreadLocal通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量 ...

  5. ThreadLocal(线程绑定)

    为保证在DAO层里的操作都在同一事务里,我们曾使用以参数的形式将Connection向下传递的方式,而ThreadLocal来创建Connection连接,避免了一直以参数的形式将Connection ...

  6. java线程——线程局部变量

    一,线程局部变量ThreadLocal的作用 用于实现线程内部的数据共享,既对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,在另一个线程访问的时候,访问的由是另一份数据. 每个线程调用 ...

  7. Java基础教程——线程局部变量

    线程局部变量 ThreadLocal,线程局部变量,不提供锁,不做线程共享,而是为每个线程提供变量的独立副本. import java.util.concurrent.*; public class ...

  8. 【java】ThreadLocal线程变量的实现原理和使用场景

    一.ThreadLocal线程变量的实现原理 1.ThreadLocal核心方法有这个几个 get().set(value).remove() 2.实现原理 ThreadLocal在每个线程都会创建一 ...

  9. [Python]threading local 线程局部变量小測试

    概念 有个概念叫做线程局部变量.一般我们对多线程中的全局变量都会加锁处理,这样的变量是共享变量,每一个线程都能够读写变量,为了保持同步我们会做枷锁处理.可是有些变量初始化以后.我们仅仅想让他们在每一个 ...

随机推荐

  1. C#的集合类型及使用技巧

    在日常开发过程中,我们不能避免的要对批量数据处理,这时候就要用到集合.集合总体上分为线性集合和非线性集合.线性集合是指元素具有唯一的前驱和后驱的数据结构类型:非线性集合是指有多个前驱和后驱的数据结构类 ...

  2. 百万年薪python之路 -- MySQL数据库之 永久修改字符串编码 与 忘了密码和修改密码

    永久修改字符集编码的方法: 在mysql安装目录下创建一个my.ini(Windows下)文件,写入下面的配置,然后重启服务端. [client] #设置mysql客户端默认字符集 default-c ...

  3. 保存为txt

    打开对话框保存为txt #region this.dDownTable = (DataTable)(this.dg1.DataContext); ) { string fName = string.E ...

  4. 像使用SQL一样对List对象集合进行排序

    在开始之前,我先卖个关子提一个问题:假设我们有一个Movie类,这个类有三个成员变量分别是starred(是否收藏), title(电影名称), rating(评分).你知道怎么对一个Movie对象组 ...

  5. Mysql UTF-8mb4字符集的问题

    官方Mysql手册链接 https://dev.mysql.com/doc/connectors/en/connector-j-reference-charsets.html Notes For Co ...

  6. OC-音乐播放器-锁屏处理

    QQ音乐播放的过程中,锁屏状态下的效果如下: 也就是说,QQ音乐播放过程中,添加锁屏远程事件的监听. 本文只记录本人知道的小知识点,不提供完整的代码. 实现的原理: (1)获取锁屏歌曲信息中心:MPN ...

  7. windows 10 环境下 使用Anaconda搭建 TensorFlow 环境

    ##大致步骤 1 安装Anaconda 2 在Anaconda中建立虚拟TensorFlow的虚拟环境 建立虚拟环境的命令是 conda  create -n tensorflow python=3. ...

  8. Apache Solr Velocity模板远程代码执行复现

    0x01漏洞描述 2019年10月31日,国外安全研究员s00py在Github公开了一个Apache Solr Velocity模板注入远程命令执行的poc. 经过研究,发现该0day漏洞真实有效并 ...

  9. 学习笔记59_python字符串处理

    python中,字符串可以使用 '或"括起来 1. 要想写成  "hellow "aaaa"  ",在python中,可以"hellow ' ...

  10. CSPS模拟 47

    考试时T1没玩明白,用一个WA90把100盖住了? T1 Emotional Flutter 题目非常蠢萌,只是注意当你把黑块前伸s距离后,应把脚的长度视为0,而不应为1. T2 Endless Fa ...