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

  在单线程应用可能会维持一个全局的数据库连接,并在程序启动时初始化这个连接对象,从而避免在调用每个方法时都传递一个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. innerHTML .innerText区别

    ().innerHtml("“):改变html元素: ().innerTEXT(”“):改变文本元素: 试验代码 <!DOCTYPE html> <html lang=&q ...

  2. C# Asp.net中简单操作MongoDB数据库(二)

    C# Asp.net中简单操作MongoDB数据库(一)    , mongodb数据库连接可以回顾上面的篇幅. 1.model类: public class BaseEntity { /// < ...

  3. canvas画圆角矩形的方法

    思路:arcTo(x1, y1, x2, y2, r) 参考:https://blog.csdn.net/shi851051279/article/details/80436851 http://ww ...

  4. encodeURIComponent 和 decodeURIComponent 对字符串url编码 用于url拼字符传值

  5. 时区切换导致quartz定时任务没有触发问题

    时区切换对Quartz的cron表达式有影响,切换的1小时内停止触发定时任务,导致sla没有定时清空内存计数,误发限流. 美国夏令时PST切换到冬令时PDT,会有时间跳变.不带时区跳变的,会出现时间重 ...

  6. C# 按不同的字节编码,通过字节数去截取字符串

    /// <summary> /// 按不同的字节编码,通过字节数去截取字符串 /// 数据库UTF-8 1个数字.字母.英文符号算1个长度 1个中文.中文符号算3个长度 /// </ ...

  7. python的高级数组之稀疏矩阵

    稀疏矩阵的定义: 具有少量非零项的矩阵(在矩阵中,若数值0的元素数目远多于非0元素的数目,并且非0元素分布没有规律时,)则称该矩阵为稀疏矩阵:相反,为稠密矩阵.非零元素的总数比上矩阵所有元素的总数为矩 ...

  8. Debian下安装中文包和输入法

    Debian下安装中文包和输入法 #aptitude install locales(没有aptitude的话可以先安装apt-get insall aptitude )  #dpkg-reconfi ...

  9. pycharm的断点调试与TODO标记

    断点调试的方法: 断点调试在程序比较大的时候调试运用的比较多 点击Pycharm软件右上角绿色三角形右边的小甲鱼图标,点击之后会弹出断点调试的界面 Debug是用来调试bug的 terminal 是终 ...

  10. nginx解决跨域

    location ~* \.(eot|ttf|woff|woff2|svg)$ { add_header Access-Control-Allow-Origin *; add_header Acces ...