1.ThreadLocal的大体理解

  ThreadLocal 又名 线程局部变量,是 Java 中一种较为特殊的 线程绑定机制,可以为每一个使用该变量的线程都提供一个变量值的副本,并且每一个线程都可以独立地改变自己的副本,而不会与其它线程的副本发生冲突。通过 ThreadLocal 存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种 隔离机制 。

  ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。这句话从字面上看起来很容易理解,但是真正理解并不是那么容易。

  先来看个例子

public class ConnectionManager {
private static Connection conn=null; public static Connection openConnection() throws SQLException{
if(conn==null){
DriverManager.getConnection(url);
}
return conn;
}
public static void closeResource() throws SQLException{
if(conn!=null){
conn.close();
}
}
}

当只有一个线程访问时,完全没有问题,但多线程呢?因为创建连接并没有加锁,并且连接共享,所以可能会有创建多个Connection,所以就会出现一种情况,当一个现场在使用连接的时候另一个线程关闭连接

解决这个问题有一个很简单的处理办法,那就是方法内创建连接(private Connection conn=null;),但是,这有一个很严重的问题,频繁的开关连接,导致服务器压力剧增,显然不是一个很好的办法

2.为了解决以上问题,我们引入ThreadLocal这个类

要了解一个类,首先要看它的成员变量,这里就不对其说明了,其次了解其主要方法

public T get() { }  
public void set(T value) { }  
public void remove() { }  
protected T initialValue() { }

先看看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();
    }

第一句是取得当前线程,然后通过getMap(t)方法获取到一个map,map的类型为ThreadLocalMap。然后接着下面获取到<key,value>键值对,注意这里获取键值对传进去的是 this,而不是当前线程t。

如果获取成功,则返回value值。

如果map为空,则调用setInitialValue方法返回value。

首先看一下getMap方法中做了什么:

   ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

可能大家没有想到的是,在getMap中,是调用当期线程t,返回当前线程t中的一个成员变量threadLocals。

那么我们继续取Thread类中取看一下成员变量threadLocals是什么:

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

实际上就是一个ThreadLocalMap,这个类型是ThreadLocal类的一个内部类,我们继续取看ThreadLocalMap的实现

     static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;             Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }

可以看到ThreadLocalMap的Entry继承了WeakReference,并且使用ThreadLocal作为键值。

然后再继续看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;
    }

很容易了解,就是如果map不为空,就设置键值对,为空,再创建Map,看一下createMap的实现:

   void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

至此,可能大部分朋友已经明白了ThreadLocal是如何为每个线程创建变量的副本的:

  首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。

初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。

然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。

下面通过一个例子来证明通过ThreadLocal能达到在每个线程中创建变量副本的效果:

package com.cn.test;  
  
public class Test {  
      
    ThreadLocal<Long> longLocal = new ThreadLocal<Long>();  
    ThreadLocal<String> stringLocal = new ThreadLocal<String>();  
  
    public void set() {  
        longLocal.set(Thread.currentThread().getId());  
        stringLocal.set(Thread.currentThread().getName());  
    }  
  
    public long getLong() {  
        return longLocal.get();  
    }  
  
    public String getString() {  
        return stringLocal.get();  
    }  
  
    public static void main(String[] args) throws InterruptedException {  
        final Test test = new Test();  
  
        test.set();  
        System.out.println(test.getLong());  
        System.out.println(test.getString());  
  
        Thread thread1 = new Thread() {  
            public void run() {  
                test.set();  
                System.out.println(test.getLong());  
                System.out.println(test.getString());  
            };  
        };  
        thread1.start();  
        thread1.join();  
  
        System.out.println(test.getLong());  
        System.out.println(test.getString());  
    }  
}
运行结果:
1  
main  
11  
Thread-0  
1  
main

  从这段代码的输出结果可以看出,在main线程中和thread1线程中,longLocal保存的副本值和stringLocal保存的副本值都不一样。最后一次在main线程再次打印副本值是为了证明在main线程中和thread1线程中的副本值确实是不同的。

ThreadLocal的应用场景

最常见的ThreadLocal使用场景为 用来解决数据库连接、Session管理等。如:

数据库连接:

private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {  
    public Connection initialValue() {  
        return DriverManager.getConnection(DB_URL);  
    }  
};  
  
public static Connection getConnection() {  
    return connectionHolder.get();  
}

  Session管理:

private static final ThreadLocal threadSession = new ThreadLocal();  
  
public static Session getSession() throws InfrastructureException {  
    Session s = (Session) threadSession.get();  
    try {  
        if (s == null) {  
            s = getSessionFactory().openSession();  
            threadSession.set(s);  
        }  
    } catch (HibernateException ex) {  
        throw new InfrastructureException(ex);  
    }  
    return s;  
}

ThreadLocal知识总结

  1)实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的;

2)为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,就像上面代码中的longLocal和stringLocal;

3)在进行get之前,必须先set,否则会报空指针异常;

如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。

ThreadLocal浅析的更多相关文章

  1. 浅析 ThreadLocal

    一.ThreadLocal类说明 ThreadLocal,很容易让人望文生义,直译"本地线程".ThreadLocal不是一个thread,是thread的局部变量.使用Threa ...

  2. 浅析ThreadLocal

    这是我的第一篇博客,条理不是很清晰,不过还是希望能对大家有所帮助. 首先明确一下这个类的作用,ThreadLocal类是用来为每个线程提供了一份变量的副本,即每个线程的局部变量.每个线程都在自己的栈空 ...

  3. 浅析Linux操作系统工作的基础

    环境:lubuntu 13.04   kernel 3.9.7 作者:SA12226265 katao 简介: 本文根据 Linux™ 系统工作基础的分析,对存储程序计算机.堆栈(函数调用堆栈)机制和 ...

  4. spring源码浅析——IOC

    =========================================== 原文链接: spring源码浅析--IOC   转载请注明出处! ======================= ...

  5. ThreadLocal 从源码角度简单分析

    目录 ThreadLcoal源码浅析 ThreadLocal的垃圾回收 Java引用 ThreadLocal的回收 各线程中threadLocalMap的回收 内存泄露问题 总结 参考 ThreadL ...

  6. ThreadLocal原理分析与代码验证

    ThreadLocal提供了线程安全的数据存储和访问方式,利用不带key的get和set方法,居然能做到线程之间隔离,非常神奇. 比如 ThreadLocal<String> thread ...

  7. Java并发包2--ThreadLocal的使用及原理浅析

    ThreadLocal 是本地线程变量,是一个以ThreadLocal对象为key,任意对象为value的存储结构. 一.使用案例 1.定义线程类MyThread,代码如下: public class ...

  8. 浅析MyBatis(三):聊一聊MyBatis的实用插件与自定义插件

    在前面的文章中,笔者详细介绍了 MyBatis 框架的底层框架与运行流程,并且在理解运行流程的基础上手写了一个自己的 MyBatis 框架.看完前两篇文章后,相信读者对 MyBatis 的偏底层原理和 ...

  9. 老生常谈系列之Aop--Spring Aop原理浅析

    老生常谈系列之Aop--Spring Aop原理浅析 概述 上一篇介绍了AspectJ的编译时织入(Complier Time Weaver),其实AspectJ也支持Load Time Weaver ...

随机推荐

  1. Rust <4>:所有权、借用、切片

    tips:栈内存分配大小固定,访问时不需要额外的寻址动作,故其速度快于堆内存分配与访问. rust 所有权规则: 每一个值在任意时刻都有且只有唯一一个所有者 当所有者离开作用域时,这个值将被丢弃 所有 ...

  2. camunda流程部署的一些简单操作

    act_re_deployment:(流程部署对象表)存放流程部署的显示名和部署时间 act_re_procdef:(流程定义表)存放流程定义的属性信息 act_ge_bytearray:(资源文件表 ...

  3. Scrapy框架: 第一个程序

    首先创建项目: scrappy start project maitian 第二步: 明确要抓取的字段items.py import scrapy class MaitianItem(scrapy.I ...

  4. 给Laravel4添加中文语系(转)

    Laravel 4 官方不附带英文以外的 validataion 错误信息翻译. 今天发现GitHub 上有一个 repository 收集不同的翻译,大家可以下载需要的翻译. GitHub项目地址: ...

  5. python面试题之如何解决验证码的问题,用什么模块,听过哪些人工打码平台?

    如何解决验证码的问题,用什么模块,听过哪些人工打码平台? PIL.pytesser.tesseract模块 平台的话有:(打码平台特殊,不保证时效性) 云打码 挣码 斐斐打码 若快打码 超级鹰 本文首 ...

  6. linux文本处理工具篇

    一.常用简单工具 cat [OPTION]... [FILE]...  -E:显示行的结束符$ -n:对显示出的每一行进行编号. -A:显示所有控制符 -s:压缩连续空行为一行 more:分页查看文件 ...

  7. Octave的安装

    本文是参考吴恩达老师的深度学习视频而做的笔记 深度学习 引言 挑战:AI真正的挑战在于解决那些对人类来说很容易执行,但很难形式化描述的问题,比如识别人们所说的话/图像中的脸/分辨苹果和梨. 解决方案: ...

  8. opensns功能详解

    <!DOCTYPE html> opensns功能详解 wmd-preview h1 { color: #0077bb; /* 将标题改为蓝色 */ } opensns功能详解 软件工程 ...

  9. spring+springmvc+mybatis框架搭建

    一.开发前准备 1)ecplise4.11.0 百度网盘:https://pan.baidu.com/s/1wO9_I52lp0mYNeNTdnj80w 提取码:booa 2)jdk1.6.0_45  ...

  10. 1.隐藏继承的成员new / 虚方法(override)/ abstract / 多态 ----- 重写

    总结: 1. 在继承上, new/override没区别 2. 在多态上,new不支持多态,override支持 在C#中改变类中相同名称的方法的实现过程中有三种方式:重载.重写和覆盖. 重载:指具有 ...