Java并发编程--理解ThreadLocal

另一篇博文:Hibernet中的ThreadLocal使用 http://www.cnblogs.com/gnivor/p/4440776.html

本文参考:
http://blog.csdn.net/lufeng20/article/details/24314381
http://www.cnblogs.com/chenying99/articles/3405161.html

  

ThreadLocal类接口很简单,只有4个方法,我们先来了解一下: 

void set(Object value)设置当前线程的线程局部变量的值。 
public Object get()该方法返回当前线程所对应的线程局部变量。 
public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。
注意,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。 
protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。
这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

 

一、知其然

synchronized这类线程同步的机制可以解决多线程并发问题,在这种解决方案下,多个线程访问到的,都是同一份变量的内容。为了防止在多线程访问的过程中,可能会出现的并发错误。不得不对多个线程的访问进行同步,这样也就意味着,多个线程必须先后对变量的值进行访问或者修改,这是一种以延长访问时间来换取线程安全性的策略。

而ThreadLocal类为每一个线程都维护了自己独有的变量拷贝。每个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了,那就没有任何必要对这些线程进行同步,它们也能最大限度的由CPU调度,并发执行。并且由于每个线程在访问该变量时,读取和修改的,都是自己独有的那一份变量拷贝,变量被彻底封闭在每个访问的线程中,并发错误出现的可能也完全消除了。对比前一种方案,这是一种以空间来换取线程安全性的策略。

来看一个运用ThreadLocal来实现数据库连接Connection对象线程隔离的例子。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;public class ConnectionManager {    private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {  @Override  protected Connection initialValue() {  Connection conn = null;  try {  conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "username", "password"); } catch (SQLException e) {  e.printStackTrace();  }  return conn;  }  };  public static Connection getConnection() {  return connectionHolder.get();  }  public static void setConnection(Connection conn) {  connectionHolder.set(conn);  }  
}  

  通过调用ConnectionManager.getConnection()方法,每个线程获取到的,都是和当前线程绑定的那个Connection对象,第一次获取时,是通过initialValue()方法的返回值来设置值的。通过ConnectionManager.setConnection(Connection conn)方法设置的Connection对象,也只会和当前线程绑定。这样就实现了Connection对象在多个线程中的完全隔离。在Spring容器中管理多线程环境下的Connection对象时,采用的思路和以上代码非常相似。

附:另一个例子 

public class TestNum {    private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){public Integer initialValue() { return 0;}};public int getNextNum(){seqNum.set(seqNum.get()+1);return seqNum.get();}public static void main(String[] args) {        TestNum sn = new TestNum();        //三个线程共享SN 产生序列号ThreadClient t1 = new ThreadClient(sn);ThreadClient t2 = new ThreadClient(sn);ThreadClient t3 = new ThreadClient(sn);t1.start();t2.start();t3.start();}
}class ThreadClient extends Thread{private TestNum sn ;public ThreadClient(TestNum sn){this.sn = sn;}public void run(){for(int i = 0 ; i < 3 ; i++){System.out.println("Thread: "+ Thread.currentThread().getName() + " sn: " + sn.getNextNum());}}    
}
View Code

  

二、知其所以然

那么到底ThreadLocal类是如何实现这种“为每个线程提供不同的变量拷贝”的呢?先来看一下ThreadLocal的set()方法的源码是如何实现的: 

    /** * Sets the current thread's copy of this thread-local variable * to the specified value.  Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of *        this thread-local. */  public void set(T value) {  Thread t = Thread.currentThread();  ThreadLocalMap map = getMap(t);  if (map != null)  map.set(this, value);  else  createMap(t, value);  }  
在这个方法内部我们看到,首先通过getMap(Thread t)方法获取一个和当前线程相关的ThreadLocalMap,然后将变量的值设置到这个ThreadLocalMap对象中,当然如果获取到的ThreadLocalMap对象为空,就通过createMap方法创建。

  线程隔离的秘密,就在于ThreadLocalMap这个类。ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(对比Map对象来理解),每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象了。

为了加深理解,我们接着看上面代码中出现的getMapcreateMap方法的实现: 

/** * Get the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param  t the current thread * @return the map */  
ThreadLocalMap getMap(Thread t) {  return t.threadLocals;  
}  /** * Create the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @param firstValue value for the initial entry of the map * @param map the map to store. */  
void createMap(Thread t, T firstValue) {  t.threadLocals = new ThreadLocalMap(this, firstValue);  
}  

 代码已经说的非常直白,就是获取和设置Thread内的一个叫threadLocals的变量,而这个变量的类型就是ThreadLocalMap,这样进一步验证了上文中的观点:每个线程都有自己独立的ThreadLocalMap对象。打开java.lang.Thread类的源代码,我们能得到更直观的证明:

/* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

 

那么接下来再看一下ThreadLocal类中的get()方法,代码是这么说的: 

/** * Returns the value in the current thread's copy of this * thread-local variable.  If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local */  
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();  
}  

  

再来看setInitialValue()方法: 

/** * Variant of set() to establish initialValue. Used instead * of set() in case user has overridden the set() method. * * @return the initial value */  private T setInitialValue() {  T value = initialValue();  Thread t = Thread.currentThread();  ThreadLocalMap map = getMap(t);  if (map != null)  map.set(this, value);  else  createMap(t, value);  return value;  }   

 

这两个方法的代码告诉我们,在获取和当前线程绑定的值时,ThreadLocalMap对象是以this指向的ThreadLocal对象为键进行查找的,这当然和前面set()方法的代码是相呼应的。

进一步地,我们可以创建不同的ThreadLocal实例来实现多个变量在不同线程间的访问隔离,为什么可以这么做?因为不同的ThreadLocal对象作为不同键,当然也可以在线程的ThreadLocalMap对象中设置不同的值了。通过ThreadLocal对象,在多线程中共享一个值和多个值的区别,就像你在一个HashMap对象中存储一个键值对和多个键值对一样,仅此而已。

设置到这些线程中的隔离变量,会不会导致内存泄漏呢?ThreadLocalMap对象保存在Thread对象中,当某个线程终止后,存储在其中的线程隔离的变量,也将作为Thread实例的垃圾被回收掉,所以完全不用担心内存泄漏的问题。在多个线程中隔离的变量,光荣的生,合理的死,真是圆满,不是么?

最后再提一句,ThreadLocal变量的这种隔离策略,也不是任何情况下都能使用的。如果多个线程并发访问的对象实例只允许,也只能创建一个,那就没有别的办法了,老老实实的使用同步机制(synchronized)来访问吧。

http://my.oschina.net/lichhao/blog/111362

转载于:https://www.cnblogs.com/gnivor/p/4904793.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/397379.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python之路——迭代器与生成器

要了解for循环是怎么回事儿&#xff0c;咱们还是要从代码的角度出发。 首先&#xff0c;我们对一个列表进行for循环。 for i in [1,2,3,4]: print(i) 上面这段代码肯定是没有问题的&#xff0c;但是我们换一种情况&#xff0c;来循环一个数字1234试试 for i in 1234print(i) 结…

DFS分布式文件系统--管理篇

DFS分布式文件系统--管理篇参考文档&#xff1a;浅谈DFS分布式文件系统DFS 命名空间 和 DFS 复制概述续DFS分布式文件系统--基础篇DFS分布式文件系统--部署篇添加命名空间服务器&#xff08;添加第二台命名空间服务器 NameSrv02)成功后如下图&#xff1a;“从显示区域隐藏命名空…

LVS负载均衡(3)——LVS工作模式与工作原理

LVS介绍及工作原理1. LVS 介绍LVS,Linux Virtual Server 的简写&#xff0c;意即 Linux 虚拟服务器&#xff0c;是一个虚拟的服务器集群系统&#xff0c;可以在 UNIX/Linux 平台下实现负载均衡集群功能。文章&#xff1a;LVS项目介绍LVS集群体系结构LVS集群的IP负载均衡技术LVS…

MyBatis入门(二)---一对一,一对多

一、创建数据库表 1.1、创建数据表同时插入数据 /* SQLyog Enterprise v12.09 (64 bit) MySQL - 5.6.27-log : Database - mybatis ********************************************************************* *//*!40101 SET NAMES utf8 */;/*!40101 SET SQL_MODE*/;/*!40014 SE…

零基础学Java的10个方法

2019独角兽企业重金招聘Python工程师标准>>> 版权声明&#xff1a;本文为北京尚学堂原创文章&#xff0c;未经允许不得转载。​ 零基础学Java只要方法得当&#xff0c;依然有机会学习好Java编程。 但作为初学者可以通过制定一些合理清晰的学习计划。 在帮你屡清楚思…

scp windows 和 linux 远程复制 (双向)

一下命令在cmd中 从w -> l : scp D:\a.txt root192.168.2.113:/home/a 从l -> w: scp root192.168.2.113:/home/aaa d:/b.txt 按说在Linux中也可以&#xff0c;但是不知道怎么的只有在winodws上行&#xff0c;在linux上就会报 ssh: connect to host 192.168.2.157 port 2…

北京尚学堂|程序员的智慧

2019独角兽企业重金招聘Python工程师标准>>> 版权声明&#xff1a;本文为北京尚学堂原创文章&#xff0c;未经允许不得转载。 编程是一种创造性的工作&#xff0c;是一门艺术。精通任何一门艺术&#xff0c;都需要很多的练习和领悟&#xff0c;所以这里提出的“智慧…

翼城中学2021高考成绩查询入口,2021年临汾中考分数线查询(4)

临汾2021年中考分数线查询 2021临汾中考录取分数线 19年临汾中考各校录取分数线 临汾各高中录取分数线 临汾2021中考录取线查询 中考信息网提供2021临汾中考分数线查询信息。临汾中考录取分数线预计7月初公布&#xff0c;届时考生可登陆临汾招生考试网官网查看分数线情况。2…

配置Tomcat使用HTTP/2

转自&#xff1a; https://zhuanlan.zhihu.com/p/21349186 前情提要&#xff1a; Tomcat高效响应的秘密(一) Sendfile与Gzip Tomcat高效响应的秘密(二) keep alive 前面高效响应的两篇&#xff0c;我们分析了Sendfile的特性以及HTTP1.1的keep-alive特性&#xff0c;基于这些功…

通过NSNotification来监听键盘弹出和弹回

在通知中心建立一个广播来监听键盘的弹出和弹回&#xff0c;在监听事件中加入触发事件的一些操作。 [[NSNotificationCenter defaultCenter]addObserver:self selector:selector(keyboardWillChange:) name:UIKeyboardWillChangeFrameNotification object:nil];[[NSNotificatio…

IT综合学习网站收集

最近整理了一下曾经使用过的IT从入门到广泛的综合类基础学习网站&#xff0c;记录下来&#xff0c;以便初学者使用&#xff1a; 1.http://www.w3school.com.cn/ 中文版基础在线学习平台 2.http://www.runoob.com/ 中文版基础在线学习平台&#xff08;和W3类似&#xff09; 3.h…

mac安装gdb及为gdb进行代码签名

1. 安装gdb GDB作为一个强大的c/c调试工具&#xff0c;一直是程序猿们的良好伴侣&#xff0c;但转到Mac os才发现竟然没有默认安装&#xff0c;所幸还有强大的homebrew工具&#xff1a; brew install homebrew/dupes/gdb然后就是漫长的等待编译安装时间了&#xff0c;安装完成后…

Python学习---Django的基础操作180116

Django创建数据库操作 django流程之model实例 settigs.py&#xff1a;更改Django2.0.1的配置&#xff0c;更新为之前的路径配置 DIRS: [os.path.join(BASE_DIR, templates)], # 设置templates的路径为Django以前版本 # DIRS: [], # 注释掉该行&#xff0c;此为Django 2.0…

订阅Jenkins的邮件列表,获取最新的信息

进入https://jenkins.io/content/mailing-lists/ 点击感兴趣的话题 选择【archive】跳转到谷歌讨论组 最后&#xff0c;点击左上角的【Subscribe】即可加入Google Groups 备注&#xff1a;其实谷歌讨论组是一个很好用的东西&#xff0c;每个人都可以上去建&#xff0c;对于集成…

英语四六级和计算机二级是一,大学里最难考证书排名,四六级和计算机根本排不进前三...

大学是我们提高自身技能最好的一个时期&#xff0c;除了平时的课程和一些社团活动之外&#xff0c;还有一件最最必不可少的事情&#xff0c;那就是考证&#xff0c;而这也是为我们以后工作打好基础&#xff0c;为自己多准备一些敲门砖。我国各个行业都有属于自己的证书&#xf…

Fedora 安装后需要做的第一件事

一直以来&#xff0c;Red Hat 系的许多教程&#xff0c;都会建议你关闭 SELinux。确实&#xff0c;启用 SELinux 可能会造成许多莫名其妙的错误。但在实际生产环境&#xff0c;甚至是用户工作站&#xff0c;Red Hat 都建议将 SELinux 设为 enforcing 模式&#xff0c;因为它在关…

html文件怎么导出stl文件,各种3D建模软件导出STL文件的小技巧(一)

很多用户在提交3D模型文件的时候&#xff0c;常常有这样的困惑&#xff1a;什么是STL 格式文件&#xff0c;怎么获取STL 格式文件呢&#xff1f;STL 格式文件是在计算机图形应用系统中&#xff0c;用于表示三角形网格的一种文件格式。它也是3D打印机在执行3D打印程序时&#xf…

angularjs 中的scope继承关系——(2)

转自&#xff1a;http://www.lovelucy.info/understanding-scopes-in-angularjs.html angularjs 中的scope继承关系 ng-include 假设在我们的 controller 中&#xff0c; $scope.myPrimitive 50; $scope.myObject {aNumber: 11}; HTML 为&#xff1a; <script type&quo…

Android界面菜单(4)—快捷菜单

2019独角兽企业重金招聘Python工程师标准>>> 快捷菜单 当用户点击界面上某个元素超过2秒后&#xff0c;将启动注册到该界面的快捷菜单。 步骤&#xff1a; 1.代码动态生成菜单 final static int CONTEXT_MENU_1 Menu.FIRST;final static int CONTEXT_MENU_2 Menu…

Echarts地图编写

1.引入echarts库文件 <script charset"utf-8" type"text/javascript" language"javascript" src"echarts-2.2.7/doc/example/www/js/echarts.js"></script> 2.在页面中新建div用于地图展示 <div id"main" st…