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. 14.Nginx四层负载均衡

    1.七层负载均衡: 根据url 调度不同的集群 url.cheng.com 10.0.0.5 10.0.0.7 /pass 10.0.0.8 /user 1.web01和web02配置 (只不过代码不 ...

  2. 02--Java Jshell的使用 最适合入门的Java教程

    JShell JShell目标 Java Shell 工具(简称:JShell)是一个用于学习Java编程语言和构建Java代码原型的交互式工具.JShell是一个Read-Evaluate-Prin ...

  3. HTTP协议详解(二)—— HTTP响应

    HTTP响应(Response) 响应与请求一样分成三个部分:响应行.响应头.响应体. 1.响应行: 格式 - HTTP/1.1 200 OK 2.响应头: 部分头属性解释 - Location:这个 ...

  4. Mybatis总结之如何自动生成数据库表结构

    一般情况下,用Mybatis的时候是先设计表结构再进行实体类以及映射文件编写的,特别是用代码生成器的时候. 但有时候不想用代码生成器,也不想定义表结构,那怎么办? 这个时候就会想到Hibernate, ...

  5. vue render

    Vue 的 render 渲染 API vue2 的 vnode tag: 当前节点的标签名 data: 当前节点是数据对象 children: 子节点,数组也是vnode 类型 text: 当前节点 ...

  6. Mysql数据库(二)Mysql数据库管理

    一 .创建数据库 1.通过CREATE DATABASE db_library;创建名称为db_library的数据库. 2.通过CREATE SCHEMA db_library1;创建名称为db_l ...

  7. (三)快速添加touch事件

    EasyTouch提供了类似UGUI的可视化点击事件,如button类似,此功能可以快速实现三维物体的旋转缩放.在场景中新建cube然后添加组件,输入quick会出现一下一个选项: quick dra ...

  8. ORCLE 创建表空间,用户,赋予角色以及授权

    1.创建表空间MMS_DATA --创建表空间和数据库文件dbf CREATE TABLESPACE MMS_DATA DATAFILE 'D:\ORADATA\ORCL\MMS_DATA.DBF' ...

  9. Unity4-用户输入

    Input是一个类,可以接收用户的输入 使用AddComponentMenu("Demo1/InputTest1"),将脚本加入到工程中. //例子: void Update() ...

  10. 学习笔记48_Memcache跟用户登录模块结合

    public interface ICacheWriter {  void AddCache(string key,object value, DateTime expDate);  void Add ...