可以想像,如果一个对象的可变的变量被多个线程访问时,必然是不安全的。

  在单线程应用可能会维持一个全局的数据库连接,并在程序启动时初始化这个连接对象,从而避免在调用每个方法时都传递一个Connection对象。ThreadUnsafe类就是这样做的:

public class ThreadUnsafe {
private static Connection connection = DriverManager.getConnection(DB_URL); public void Connection getConnection{ /* 在多线程应用中,connection 在被多个线程访问 */
return connection;
}
}

  但是JDBC连接对象不一定是线程安全的,在多个线程访问到Connection时,就可能出现安全问题。为了解决这个问题,ThreadLocal类提供了安全的做法。

  通过将JDBC的Connection对象封装在ThreadLocal对象中,当每个线程访问需要Connection对象时,ThreadLocal对象返回的是一个副本。

public class ThreadUnsafe {
private static ThreadLocal<Connection> connectionHodler = new ThreadLocal<>{
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
} public void Connection getConnection{ /* 即使多个线程可以访问,依然安全 */
return connectionHolder.get();
}
}

ThreadLocal是如何实现这种功能? 

  首先,在Thread类中有一个threadLocals的实例变量,这是一个Map,保存了与线程相关的ThreadLocal对象封装的变量。

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

  当线程初次调用ThreadLocal对象的get方法时,就会调用initialValue()来获取初始值。

    /**
* 返回ThreadLocal封装的对象。*/
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) { /* 首次调用map为null */
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue(); /* 首次调用的返回值 */
} /**
* 初始化封装在ThreadLocal中对象的值。*/
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value); //为什么键值是ThreadLocal对象?,因为一个线程对象可能有使用多个ThreadLocal封闭的变量
else
createMap(t, value);
return value;
} /**
* 更新封装在ThreadLocal中对象的值*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
} public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
  
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
} /**
* 创建一个Map,用于保存ThreadLocal和其封装的对象。*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

  注意:ThreadLocalMap在ThreadLocal类中声明,却是在Thread类中使用的,原因在于,当线程结束时,这些特定于线程的值保存在Thread对象中,当线程终止后,这些值会作为垃圾回收。

  ThreadLocal类实现的是一种线程封闭技术。将变量封闭在单线程中,从而避免同步。

参考: 《Java Concurrency in Practice》 P35&P37

ThreadLocal的意义和实现的更多相关文章

  1. 解析ThreadLocal

    如果定义了一个单实例的java bean,它有若干属性,但是有一个属性不是线程安全的,比如说HashMap.并且碰巧你并不需要在不同的线程中共享这个属性,也就是说这个属性不存在跨线程的意义.那么不推荐 ...

  2. ThreadLocal实现方式&使用介绍—无锁化线程封闭

    原文出处: xieyu_zy 虽然现在可以说很多程序员会用ThreadLocal,但是我相信大多数程序员还不知道ThreadLocal,而使用ThreadLocal的程序员大多只是知道其然而不知其所以 ...

  3. 切换数据库+ThreadLocal+AbstractRoutingDataSource 一

    最近项目用的数据库要整合成一个,所以把多源数据库切换的写法要清除掉.所以以下记载了多远数据库切换的用法及个人对源码的理解. 框架:Spring+mybatis+vertx,(多源数据库切换的用法不涉及 ...

  4. 【Java】ThreadLocal细节分析

    ThreadLocal通过中文解释就是线程本地变量,是线程的一个局部变量.根据哲学家黑格尔“的存在即合理”的说法,ThreadLocal的出现肯定是有它的意义,它的出现也是因为多线程的一个产物.Thr ...

  5. ThreadLocal用法和实现原理

    如果你定义了一个单实例的java bean,它有若干属性,但是有一个属性不是线程安全的,比如说HashMap.并且碰巧你并不需要在不同的线程中共享这个属性,也就是说这个属性不存在跨线程的意义.那么你不 ...

  6. ThreadLocal解析

    ThreadLocal 如果定义了一个单实例的java bean,它有若干属性,但是有一个属性不是线程安全的,比如说HashMap.并且碰巧你并不需要在不同的线程中共享这个属性,也就是说这个属性不存在 ...

  7. 深入ThreadLocal之三(ThreadLocal可能引起的内存泄露)

    threadlocal里面使用了一个存在弱引用的map,当释放掉threadlocal的强引用以后,map里面的value却没有被回收.而这块value永远不会被访问到了. 所以存在着内存泄露. 最好 ...

  8. 【转载】Java中如何写一段内存泄露的程序 & ThreadLocal 介绍和使用

    可以参考这段文章: link A1:通过以下步骤可以很容易产生内存泄露(程序代码不能访问到某些对象,但是它们仍然保存在内存中): 上文中提到了使用ThreadLocal造成了内存泄露,但是写的不清不楚 ...

  9. ThreadLocal实现方式&使用介绍---无锁化线程封闭

    虽然现在可以说很多程序员会用ThreadLocal,但是我相信大多数程序员还不知道ThreadLocal,而使用ThreadLocal的程序员大多只是知道其然而不知其所以然,因此,使用ThreadLo ...

随机推荐

  1. JS之数组的几个不 low 操作

    JS之数组的几个不 low 操作 1.扁平化n维数组 1)终极篇 [1,[2,3]].flat(2) //[1,2,3] [1,[2,3,[4,5]].flat(3) //[1,2,3,4,5] [1 ...

  2. 如何用anysend发wirshark的报文

    可以复制 抓包的的报文的 hex txt文档  粘贴到 anysend 继续发送

  3. 如何print 输出不换行(2 和 3 处理方式 不一样)

    2.7    正常情况下print输出的时候会自动进行换行处理,我们肯定有时候会有输出不换行的需求, 下面开始介绍如何不换行输出: 例子: print("hello world") ...

  4. init();

    <script language="javascript" type="text/javascript">window.onload = funct ...

  5. [Day20]Map接口、可变参数、Collections

    1.Map接口-元素成对存在,每个元素由健与值两部分组成,通过键可以找所对应的值 1.1 Map子类 (1)HashMap<K,V>:存储数据所用的哈希表结构,元素的存取数据顺序不能保证一 ...

  6. tcpdump我的交叉编译(mips)

    一.libpcap交叉编译 1.下载libpcap-1.8.1(http://www.tcpdump.org/) 2.解压 3.修改configure文件 a.注释掉 #if test -z &quo ...

  7. shell 字符串比较 算数比较 文件条件测试

    set-group-id即set-gid -->授予了程序其所在组的访问权限 set-user-id即set-uid -->授予了程序其拥有者的访问权限而不是其使用者的访问权限 set-g ...

  8. Docker Machine批量安装docker host

    Dokcer Machine Docker Machine 可以批量安装和配置 docker host   提高docker的安装效率   同时减少人工安装操作的失误 [root@localhost ...

  9. layer倒计时弹框/弹层 DEMO

    layer.msg("提示语...", { time: 5000, shade: 0.6, success: function (layero, index) { var msg ...

  10. 记一次Linux服务器因redis漏洞的挖矿病毒入侵

    中毒原因,redis bind 0.0.0.0 而且没有密码,和安全意识太薄弱. 所以,redis一定要设密码,改端口,不要用root用户启动,如果业务没有需要,不要bind 0.0.0.0!!!!! ...