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. 18.Tomcat基本应用

    1.JVM基本介绍 JAVA编译型 ---> 编译 C 编译型---> linux --->编译一次 windows --->编译一次 macos ubuntu 跨平台 移值型 ...

  2. Netty源码分析之ChannelPipeline(一)—ChannelPipeline的构造与初始化

    Netty中ChannelPipeline实际上类似与一条数据管道,负责传递Channel中读取的消息,它本质上是基于责任链模式的设计与实现,无论是IO事件的拦截器,还是用户自定义的ChannelHa ...

  3. thinkcmf5 支付宝接口操作

    这是我自己摸索再结合网上搜索的一大堆内容搞出来的,官方的文件对于新手来说很难理解,什么文件放什么位置,该怎么引用,都要一一尝试,我这就直接列出来了,照样放就是了.同样适用于tp5,我这个也是tp5的 ...

  4. 算法<初级> - 第二章 队列、栈、哈希表相关问题

    算法 - 第二章 数据结构 题目一 用数组实现大小固定的队列和栈(一面题) 数组实现大小固定栈 /*** * size是对头索引(initSize是固定大小) 也是当前栈大小 * size=下个进队i ...

  5. 微信小程序和微信小程序之间的跳转和传参示例代码附讲解

    一:微信小程序跳转 使用限制 需要用户触发跳转 从 2.3.0 版本开始,若用户未点击小程序页面任意位置,则开发者将无法调用此接口自动跳转至其他小程序. 需要用户确认跳转 从 2.3.0 版本开始,在 ...

  6. Newtonsoft—Json.NET常用方法简述

    Json.NET常用方法汇总(可解决日常百分之90的需求) 0.Json.NET基础用法 首先去官网下载最新的Newtonsoft.Json.dll(也可以使用VS自带的NuGet搜索Json.NET ...

  7. Yii 数据库重连告别General error: 2006 MySQL server has gone away

    General error: 2006 MySQL server has gone away 错误原因 制造错误 解决办法 最新办法 错误原因 Mysql has gone away MySQL 服务 ...

  8. Sqlsever新增作业执行计划傻瓜式操作

    开启数据库代理,启动不了请检查数据库服务的代理是否开启  建议Sqlserver2008以上的版本 完整步骤如下: 查看效果: 10秒以后再来查询:发现数据有多了一些,是不是很简单,嘻嘻!

  9. LeetCode刷题笔记(6)按照索引计算int[] 数组中的和([Time Limit Exceeded]问题)

    Easy303 Easy633 package easy; public class e303 { private int[] sums; public e303(int[] nums) { sums ...

  10. CSS盒子模型+box-sizing

    当对文档进行布局时,浏览器渲染引擎会根据css-Box模型(CSS Basic Box model)将所有元素表示为一个矩形盒子.CSS决定这些盒子的大小,位置以及属性(颜色,背景,边框尺寸) 标准盒 ...