ThreadLocal深入理解二
转载:http://doc00.com/doc/101101jf6
今天在看之前转载的博客:ThreadLocal的内部实现原理.突然有个疑问, 按照threadLocal的原理, 当把一个对象存入到ThreadLocal中, 也只是存的对象的引用.这个多个线程之间不还是会共享这个对象吗?一个线程对这个对象进行修改, 另一个线程也会有影响啊! 因为各线程中只是存的这个对象的引用. 带着这个疑问, 看了下别人的解答,感觉下面这个讲得听清楚,自己对ThreadLocal的理解更深了一点. 转载如下
大家通常知道,ThreadLocal类可以帮助我们实现线程的安全性,这个类能使线程中的某个值与保存值的对象关联起来。ThreadLocal提供了get与set等访问接口或方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回由当前执行线程在调用set时设置的最新值。从概念上看,我们把ThreadLocal<T>理解成一个包含了Map<Thread,T>的对象,其中Map的key用来标识不同的线程,而Map的value存放了特定该线程的某个值。但是ThreadLocal的实现并非如此,我们以这样的理解方式去使用ThreadLocal也并不能实现真正的线程安全。
下面我们举一个例子进行说明,Number是拥有一个int型成员变量的类:
public class Number {
private int num;
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
@Override
public String toString() {
return "Number [num=" + num + "]";
}
}
NotSafeThread是一个实现了Runable接口的类,其中我们创建了一个ThreadLocal<Number>类型的变量value,用来存放不同线程的num值,接着我们用线程池的方式启动了5个线程,我们希望使用ThreadLocal类为5个不同的线程都存放一个Number类型的副本,根除对变量的共享,并且在调用ThreadLocal类的get()方法时,返回与线程关联的Number对象,而这些Number对象我们希望它们都能跟踪自己的计数值:
public class NotSafeThread implements Runnable {
public static Number number = new Number();
public static int i = 0;
public void run() {
//每个线程计数加一
number.setNum(i++);
//将其存储到ThreadLocal中
value.set(number);
//输出num值
System.out.println(value.get().getNum());
}
public static ThreadLocal<Number> value = new ThreadLocal<Number>() {
};
public static void main(String[] args) {
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
newCachedThreadPool.execute(new NotSafeThread());
}
}
}
启动程序:输出结果
0
1
2
3
4
看起来一切正常,每个线程好像都有自己关于Number的存储空间,但是我们简单的在输出前加一个延时:
public class NotSafeThread implements Runnable {
public static Number number = new Number();
public static int i = 0;
public void run() {
//每个线程计数加一
number.setNum(i++);
//将其存储到ThreadLocal中
value.set(number);
//延时2秒
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
}
//输出num值
System.out.println(value.get().getNum());
}
public static ThreadLocal<Number> value = new ThreadLocal<Number>() {
};
public static void main(String[] args) {
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
newCachedThreadPool.execute(new NotSafeThread());
}
}
}
运行程序,输出:
4
4
4
4
4
为什么每个线程都输出4?难道他们没有独自保存自己的Number副本吗?为什么其他线程还是能够修改这个值?我们看一下ThreadLocal的源码:
public void set(Object obj)
{
Thread thread = Thread.currentThread();//获取当前线程
ThreadLocalMap threadlocalmap = getMap(thread);
if(threadlocalmap != null)
threadlocalmap.set(this, obj);
else
createMap(thread, obj);
}
其中getMap方法:
ThreadLocal.ThreadLocalMap getMap(Thread thread)
{
return thread.inheritableThreadLocals;//返回的是thread的成员变量
}
可以看到,这些特定于线程的值是保存在当前的Thread对象中,并非保存在ThreadLocal对象中。并且我们发现Thread对象中保存的是Object对象的一个引用,这样的话,当有其他线程对这个引用指向的对象做修改时,当前线程Thread对象中保存的值也会发生变化。这也就是为什么上面的程序为什么会输出一样的结果:5个线程中保存的是同一Number对象的引用,在线程睡眠2s的时候,其他线程将num变量进行了修改,因此它们最终输出的结果是相同的。
那么,ThreadLocal的“为每个使用该变量的线程都存有一份独立的副本,因此get总是返回由当前执行线程在调用set时设置的最新值。”这句话中的“独立的副本”,也就是我们理解的“线程本地存储”只能是每个线程所独有的对象并且不与其他线程进行共享,大概是这样的情况:
public static ThreadLocal<Number> value = new ThreadLocal<Number>() {
public Number initialValue(){//为每个线程保存的值进行初始化操作
return new Number();
}
};
或者
public void run() {
value.set(new Number());
}
好吧...这个时候估计你会说:那这个ThreadLocal有什么用嘛,每个线程都自己new一个对象使用,只有它自己使用这个对象而不进行共享,那么程序肯定是线程安全的咯。这样看起来我不使用ThreadLocal,在需要用某个对象的时候,直接new一个给本线程使用不就好咯。
确实,ThreadLocal的使用不是为了能让多个线程共同使用某一对象,而是我有一个线程A,其中我需要用到某个对象o,这个对象o在这个线程A之内会被多处调用,而我不希望将这个对象o当作参数在多个方法之间传递,于是,我将这个对象o放到TheadLocal中,这样,在这个线程A之内的任何地方,只要线程A之中的方法不修改这个对象o,我都能取到同样的这个变量o。
再举一个在实际中应用的例子,例如,我们有一个银行的BankDAO类和一个个人账户的PeopleDAO类,现在需要个人向银行进行转账,在PeopleDAO类中有一个账户减少的方法,BankDAO类中有一个账户增加的方法,那么这两个方法在调用的时候必须使用同一个Connection数据库连接对象,如果他们使用两个Connection对象,则会开启两段事务,可能出现个人账户减少而银行账户未增加的现象。使用同一个Connection对象的话,在应用程序中可能会设置为一个全局的数据库连接对象,从而避免在调用每个方法时都传递一个Connection对象。问题是当我们把Connection对象设置为全局变量时,你不能保证是否有其他线程会将这个Connection对象关闭,这样就会出现线程安全问题。解决办法就是在进行转账操作这个线程中,使用ThreadLocal中获取Connection对象,这样,在调用个人账户减少和银行账户增加的线程中,就能从ThreadLocal中取到同一个Connection对象,并且这个Connection对象为转账操作这个线程独有,不会被其他线程影响,保证了线程安全性。
代码如下:
public class ConnectionHolder {
public static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
};
public static Connection getConnection(){
Connection connection = connectionHolder.get();
if(null == connection){
connection = DriverManager.getConnection(DB_URL);
connectionHolder.set(connection);
}
return connection;
}
}
在框架中,我们需要将一个事务上下文(Transaction Context)与某个执行中的线程关联起来。通过将事务上下文保存在静态的ThreaLocal对象中(这个上下文肯定是不与其他线程共享的),可以很容易地实现这个功能:当框架代码需要判断当前运行的是哪一个事务时,只需从这个ThreadLocal对象中读取事务上下文。这种机制很方便,因为它避免了在调用每个方法时都需要传递执行上下文信息,然而这也将使用该机制的代码与框架耦合在一起。
ThreadLocal深入理解二的更多相关文章
- ThreadLocal深入理解一
转载:http://www.cnblogs.com/dolphin0520/p/3920407.html 想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨下ThreadLocal的使 ...
- ThreadLocal的理解与应用场景分析
对于Java ThreadLocal的理解与应用场景分析 一.对ThreadLocal理解 ThreadLocal提供一个方便的方式,可以根据不同的线程存放一些不同的特征属性,可以方便的在线程中进行存 ...
- 对ThreadLocal的理解
参考文档:https://www.cnblogs.com/moonandstar08/p/4912673.html 一.定义:线程本地变量,每个线程中的变量相互独立,互不影响. 官方定义: 1 ...
- Python中ThreadLocal的理解与使用
一.对 ThreadLocal 的理解 ThreadLocal,有的人叫它线程本地变量,也有的人叫它线程本地存储,其实意思一样. ThreadLocal 在每一个变量中都会创建一个副本,每个线程都可以 ...
- java中threadlocal的理解
[TOC] #java中threadlocal的理解##一.threadlocal的生命周期和ThreadLocalMap的生命周期可以吧TreadLocal看做是一个map来使用,只不过这个map是 ...
- Java中的ThreadLocal深入理解
提到ThreadLocal,有些Android或者Java程序员可能有所陌生,可能会提出种种问题,它是做什么的,是不是和线程有关,怎么使用呢?等等问题,本文将总结一下我对ThreadLocal的理解和 ...
- 用三维的视角理解二维世界:完美解释meshgrid函数,三维曲面,等高线,看完你就懂了。...
完美解释meshgrid函数,三维曲面,等高线 #用三维的视角理解二维世界 #完美解释meshgrid函数,三维曲面,等高线 import numpy as np import matplotlib. ...
- ThreadLocal系列(二)-InheritableThreadLocal的使用及原理解析
ThreadLocal系列之InheritableThreadLocal的使用及原理解析(源码基于java8) 上一篇:ThreadLocal系列(一)-ThreadLocal的使用及原理解析 下一篇 ...
- 来讲讲你对ThreadLocal的理解
前言 面试的时候被问到ThreadLocal的相关知识,没有回答好(奶奶的,现在感觉问啥都能被问倒),所以我决定先解决这几次面试中都遇到的高频问题,把这几个硬骨头都能理解的透彻的说出来了,感觉最起码不 ...
随机推荐
- [转]gitHub客户端Desktop的安装使用总结 ---基础篇
gitHub客户端Desktop的安装使用总结 ---基础篇 发表于2015/12/11 11:41:57 8399人阅读 分类: Android之应用实战 这段时间想把我写的东西上传到github ...
- 如何彻底卸载MySQL
如何彻底卸载MySQL 下面给出完整的卸载MySQL 5.1的卸载方法: 1.控制面板里的增加删除程序内进行删除 2.删除MySQL文件夹下的my.ini文件,如果备份好,可以直接将文件夹全部删除 3 ...
- CCF真题之网络延时
201503-4 问题描述 给定一个公司的网络,由n台交换机和m台终端电脑组成,交换机与交换机.交换机与电脑之间使用网络连接.交换机按层级设置,编号为1的交换机为根交换机,层级为1.其他的交换机都连接 ...
- java 控制器向页面传值方式
1.单个字符串 @RequestMapping(value = "list", method = RequestMethod.GET) public ModelAndView li ...
- jvm排查工具
jps jps -mvl --查看java的ps进程. jstack 打印一个线程堆栈信息 top -H -p pid1 -> 得到占用资源大的pid2 jstack pid1 | grep & ...
- -XX:+PrintHeapAtGC 每次一次GC后,都打印堆信息
-XX:+PrintHeapAtGC每次一次GC后,都打印堆信息 {Heap before GC invocations=0 (full 0): def new generation total ...
- CDC spyglass
SoC中会有着几百的clock domains,millions的async data crossing. Glitch等cdc问题是netlist level simulation的主要目的. CD ...
- [CentOS] 使用gitolite搭建git服务端
1. 创建用户 #创建gitadmin用户 useradd gitadmin #设置gitadmin密码 passwd gitadmin #创建git用户 useradd git #设置git密码 p ...
- 把Nodepad++添加进右键菜单
1.运行注册表编辑器:开始->运行->regedit 2.找到HKEY_CLASSES_ROOT/*/shell 3.右击shell,选择 新建->项 4.项的名字为Edit wit ...
- 【crunch bang】国内源
/etc/apt/sources.list 因为测试需要,装完Debian7 后,更新为163的源,但是后来装软件时,一些软件依赖包还是装不上.后来把163源稍加改动,就好用了.163源内容如下: d ...