前言

  在java web项目中,经常会使用到单例对象,从服务器启动那一时刻就实例化全局对象。然后会对某些全局对象的属性进行修改之类的操作,但是我们知道项目一般都是部署到tomcat、Jboss之类的服务器上。浏览器的每个请求就是一个新的线程,这样如果  对全局对象的属性进行修改并使用,很可能就会造成数据不一致的错误问题。那怎么保证各自线程能正确使用自己修改过的共享变量呢?这时让我们想到ThreadLocal,那ThreadLocal是什么,为何能有如此神奇的行为呢?带着这个问题我们直接进入主题。

什么是ThreadLocal?

  ThreadLocal是java.lang包下面的一个类。见名知意,局部的线程。它能避免发生多线程对共享变量修改造成的数据错误问题!

ThreadLocal的底层原理

  ThreadLocal能使变量值和线程对象关联起来,保证线程封闭。下面是ThreadLocal类主要的方法:

 public T get();
public void set(T value);
public void remove();
private T setInitialValue();

  get方法是获取保存在ThreadLocal中当前线程设置的共享变量副本值;

  set方法是用来设置当前线程的共享变量副本;

  set方法

 public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

  set方法会获取当前线程(线程中有一个属性threadLocals,该属性属于ThreadLocal.ThreadLocalMap类)。如果当前线程的ThreadLocalMap对象不为空,直接把修改的值存放到ThreadLocalMap中;如果为空,则先实例化ThreadLocalMap对象,再存值。

ThreadLocalMap和HashMap相似,也是通过哈希表的数据结构来保存数据(数组加链表)。从set方法可以知道,一个线程有且只会创建一个ThreadLocalMap对象,线程会把修改后的变量值(变量副本)保存到ThreadLocalMap中。

void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
} ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY -1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}

  get方法

 public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}

  get方法会获取到当前线程ThreadLocalMap对象中保存的副本值。

注意:同一个ThreadLocal对象下,保存在ThreadLocalMap中的Entry对象下标相同。因为下标计算=threadLocalHashCode & (len-1); threadLocalHashCode 和len都是相同的。

  remove方法

public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}

  获取当前线程的ThreadLocalMap对象,对象不为空,则调用remove(this);看看这个方法做了什么。

private void remove(ThreadLocal key) {
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)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i); // 清空Entry[] 数组对应下标的对象
return;
}
}
}

  remove的目的是帮助GC清除多余对象,避免造成内存溢出。

实际项目中的运用

  项目中有一个全局对象HttpJsonResource,服务启动的时候初始化。初始化后会维护一个HttpJsonService对象到属性中,通过get方法能获取到HttpJsonService对象。看代码分析:

HttpJsonService service = getHttpJsonService(context, JYLS_URL);
String originUrl = service.getHttpURL();
// HttpJsonService 中的httpURL是个全局变量,originUrl是公用的套接字 String newUrl = originUrl + this.urlSuffix; // urlSuffix是不同的服务编码
.....
使用HttpURLConnection调用其它服务;
HttpURLConnection conn;
URL url = new URL(originUrl + threadLocalURL.get());
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod(super.getReqMethod().toUpperCase());
conn.setRequestProperty("Connection", "close");
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/json;charset=utf-8");//设置参数类型是json格式
// 启动了一个线程
HttpCommProcessThread task = new HttpCommProcessThread(this, conn, reqData, super.getReqMethod());
task.startUp();
// 阻塞主线程
task.waitForData(timeOut);
.
.
// 最后把HttpURL设置为原始的公用值
service.setHttpURL(originUrl);

  代码逻辑主要是获取到一个公用的http请求地址httpURL,然后拼接上服务编码,组成一个新的http请求去调用外部服务,调用完之后又把httpURL修改为原始值。看上去是OK的!但是有一个问题,调用接口比如超时了,在这一段时间内,A请求线程还被阻塞,又有一个请求B来调用,这时上A请求没来得及把httpURL修改为原始值,B请求又在A请求修改为新的url基础上进行拼接,导致utl错误。

ThreadLocal的原理与使用的更多相关文章

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

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

  2. ThreadLocal的原理和在框架中的应用

    ThreadLocal的原理和在框架中的应用 博客分类: java基础 框架多线程SpringthreadDAO  概述      我们知道Spring通过各种DAO模板类降低了开发者使用各种数据持久 ...

  3. ThreadLocal的原理及产生的问题

    点赞再看,养成习惯,微信搜索「小大白日志」关注这个搬砖人. 文章不定期同步公众号,还有各种一线大厂面试原题.我的学习系列笔记. ThreadLocal的原理 特点 ThreadLocal和Sychro ...

  4. ThreadLocal 工作原理、部分源码分析

    1.大概去哪里看 ThreadLocal 其根本实现方法,是在Thread里面,有一个ThreadLocal.ThreadLocalMap属性 ThreadLocal.ThreadLocalMap t ...

  5. ThreadLocal工作原理

    原文出处: imzoer 在这篇文章中,总结了一下面试过程中遇到的关于ThreadLocal的内容.总体上说,这样回答,面试算是过得去了.但是,这样的回答,明显仅仅是背会了答案,而没有去研究Threa ...

  6. ThreadLocal的原理,源码深度分析及使用

    文章简介 ThreadLocal应该都比较熟悉,这篇文章会基于ThreadLocal的应用以及实现原理做一个全面的分析 内容导航 什么是ThreadLocal ThreadLocal的使用 分析Thr ...

  7. 对ThreadLocal实现原理的一点思考

    前言 在<透彻理解Spring事务设计思想之手写实现>中,已经向大家揭示了Spring就是利用ThreadLocal来实现一个线程中的Connection是同一个,从而保证了事务.本篇博客 ...

  8. 【原理】Java的ThreadLocal实现原理浅读

    当前线程的值传递,ThreadLocal 通过ThreadLocal设值,在线程内可获取,即时获取值时在其它Class或其它Method. public class BasicUsage { priv ...

  9. ThreadLocal实现原理

      一.ThreadLocal介绍     这是一个线程的局部变量.也就是说,只有当前线程可以访问.既然是只有当前线程可以访问的数据,自然是线程安全的.     为每一个线程分配不同的对象,需要在应用 ...

  10. ThreadLocal使用原理、注意问题、使用场景

    想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨下ThreadLocal的使用方法和实现原理.首先,本文先谈一下对ThreadLocal的理解,然后根据ThreadLocal类的源码 ...

随机推荐

  1. ArcGIS中国工具3.0正式发布

    ArcGIS中国工具3.0正式发布,新功能有 1.  支持面积分割(见4.6),见https://weibo.com/tv/v/HsM2ksYY3?fid=1034:4368578107884427 ...

  2. [java] 将整数在千分位或万分位以逗号分隔表示

    简单使用DecimalFormat的功能就能做到了,代码如下: package com.testEmp; import java.text.DecimalFormat; public class Nu ...

  3. 安装win10提示“我们无法创建新的分区,也找不到现有分区”

    用U盘安装操作系统,但是遇到了这种问题. 出现这种情况可能是硬盘格式通过指令写死了,所以我们需要通过指令把格式清零, 补充: 其实系统找不到系统分区这种情况其实就是引导程序出了问题,可以用大白菜这种w ...

  4. 小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_5-3.微信Oauth2.0交互流程讲解

    笔记 3.微信Oauth2.0交互流程讲解     简介:讲解微信Oauth2.0交互流程              参考:https://open.weixin.qq.com/cgi-bin/sho ...

  5. Qt编写自定义控件16-魔法老鼠

    前言 五一期间一直忙着大屏电子看板软件的开发,没有再去整理控件,今天已经将大屏电子看板的所有子窗口都实现了任意停靠和双击独立再次双击最大化等功能,过阵子有空再写一篇文章介绍其中的技术点.魔法老鼠控件, ...

  6. Collection Map Java数据结构

    Collection Map 框架图 Collection          接口的接口   对象的集合 ├ List                   子接口      按进入先后有序保存   可 ...

  7. spark简单快速学习及打开UI界面---1

    1.远程集群测试 import org.apache.spark.{SparkContext, SparkConf} import scala.math.random /** * 利用spark进行圆 ...

  8. 精通Dubbo——dubbo2.0源码中的设计模式与SPI介绍

    Dubbo源码包介绍当我们从github把Dubbo源码下载下来之后有如下源码包   下面来说明每个包的作用,以便我们有目的的阅读代码 dubbo-admin dubbo管理平台源码包,用来管理dub ...

  9. mysql锁表机制分析

    http://blog.csdn.net/u010942020/article/details/51925653

  10. 写linux脚本你怎么能不知道位置参数!?

    在写shell脚本的时候,我们经常会手动设置参数,然后对我们的输入的这些参数进行处理和分析,那么这个东东到底值怎么使用的呢? 1.$n $0代表命令本身,$1-9代表接受的第1-9个参数,10以上需要 ...