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. solr学习篇(一) solr7.4 安装配置篇

    目录: solr简介 solr安装 创建core 1.solr简介 solr是企业级应用的全文检索项目,它是基于Apache Lucence搜索引擎开发出来的用于搜索的应用工程 运行环境:solr需要 ...

  2. Html.CSS.JavaScript 学习经验

    HTML里面 不要使用 document.getElementsByName() 来获取 元素,会出错. 使用 document.getElementById()更好一些. substring()首字 ...

  3. (day29) 进程互斥锁 + 线程

    目录 进程互斥锁 队列和堆栈 进程间通信(IPC) 生产者和消费者模型 线程 什么是线程 为什么使用线程 怎么开启线程 线程对象的属性 线程互斥锁 进程互斥锁 进程间数据不共享,但是共享同一套文件系统 ...

  4. FastJson的使用心得

      本文为早前整理,参考文献已找不到,如有侵权请与我联系,添加参考链接. 一丶基本使用 1.1主要API fastjson入口类是com.alibaba.fastjson.JSON,主要的API是JS ...

  5. vue学习笔记-遗留问题记录

    Node.js是什么?对node.js的理解 官网解释:Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时. 这是一种通过JavaScript语言开发web服务端的东 ...

  6. 转:XSS和CSRF原理及防范

    原文地址:http://www.freebuf.com/articles/web/39234.html 随着Web2.0.社交网络.微博等等一系列新型的互联网产品的诞生,基于Web环境的互联网应用越来 ...

  7. Web for pentester_writeup之File Upload篇

    Web for pentester_writeup之File Upload篇 File Upload(文件上传) Example 1 直接上传一句话木马,使用蚁剑连接 成功连接,获取网站根目录 Exa ...

  8. javascript创建一个基于数组的栈结构

    栈是一种遵从后进先出(LIFO)原则的有序集合.新添加或待删除的元素都保存在栈的同 一端,称作栈顶,另一端就叫栈底.在栈里,新元素都靠近栈顶,旧元素都接近栈底. 栈拥有以下方法: push(eleme ...

  9. Linux 项目 shell 自动获取报告本机IP (1) | 通过shell 自动获取报告本机IP

    由于电脑设置静态IP经常出现链接不上网络,动态IP又非常不方便,故有了这个想法并实现 原理: Linux,包含PC机器,树莓派等,通过shell 自动获取报告本机IP  | 通过 Mutt+Msmtp ...

  10. python3学习,有c++的基础

    # 为注释一行 ''' ''' 和 """ """为注释多行 用缩进表示代码块,不用{},同一等级代码用的缩进数一致 一条语句写在多行:a= ...