`
elvis4139
  • 浏览: 148161 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

认识ThreadLocal

阅读更多
知其然

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对象时,采用的思路和以上代码非常相似。

知其所以然

那么到底ThreadLocal类是如何实现这种“为每个线程提供不同的变量拷贝”的呢?先来看一下ThreadLocal的set()方法的源码是如何实现的:
&nbsp; &nbsp; /**
&nbsp; &nbsp; &nbsp;* Sets the current thread's copy of this thread-local variable
&nbsp; &nbsp; &nbsp;* to the specified value. &nbsp;Most subclasses will have no need to&nbsp;
&nbsp; &nbsp; &nbsp;* override this method, relying solely on the {@link #initialValue}
&nbsp; &nbsp; &nbsp;* method to set the values of thread-locals.
&nbsp; &nbsp; &nbsp;*
&nbsp; &nbsp; &nbsp;* @param value the value to be stored in the current thread's copy of
&nbsp; &nbsp; &nbsp;* &nbsp; &nbsp; &nbsp; &nbsp;this thread-local.
&nbsp; &nbsp; &nbsp;*/
&nbsp; &nbsp; public void set(T value) {
&nbsp; &nbsp; &nbsp; &nbsp; Thread t = Thread.currentThread();
&nbsp; &nbsp; &nbsp; &nbsp; ThreadLocalMap map = getMap(t);
&nbsp; &nbsp; &nbsp; &nbsp; if (map != null)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; map.set(this, value);
&nbsp; &nbsp; &nbsp; &nbsp; else
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; createMap(t, value);
&nbsp; &nbsp; }


没有什么魔法,在这个方法内部我们看到,首先通过getMap(Thread t)方法获取一个和当前线程相关的ThreadLocalMap,然后将变量的值设置到这个ThreadLocalMap对象中,当然如果获取到的ThreadLocalMap对象为空,就通过createMap方法创建。

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

为了加深理解,我们接着看上面代码中出现的getMap和createMap方法的实现:
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

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

    /**
     * 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变量的这种隔离策略,也不是任何情况下都能使用的。如果多个线程并发访问的对象实例只允许,也只能创建那么一个,那就没有别的办法了,老老实实的使用同步机制来访问吧。
分享到:
评论

相关推荐

    Android 详解ThreadLocal及InheritableThreadLocal

    理解ThreadLocal的关键在于认识到它是线程局部的,每个线程都有自己的独立副本,而不是所有线程共享一个全局变量。这使得ThreadLocal成为在多线程环境下维护线程安全状态的一种有效手段,尤其在处理如Handler、...

    Spring基于ThreadLocal的“资源-事务”线程绑定设计的缘起

    题目起的有些拗口了,简单说,这篇文章想要解释Spring为什么会选择使用ThreadLocal将资源和事务绑定到线程上,这背后有着什么样的起因和设计动机,通过分析帮助大家更清晰地认识Spring的线程绑定机制。访问任何带有...

    通向架构师的道路

    - **架构基础**:书中首先介绍了J2EE工程的通用架构,包括了分层结构和组件交互原则,为读者建立起对Web应用架构的初步认识。 #### Apache与Tomcat整合 - **Web服务器与应用服务器**:介绍了Apache HTTP服务器与...

    Java并发编程原理精讲 视频教程 下载 因为太大 百度网盘链接4.zip

    │ │ 01 认识线程.mp4 │ │ 02 线程和进程的关系.mp4 │ │ 03 并发和并行的区别.mp4 │ │ 04 并发编程的应用场景和风险.mp4 │ │ │ ├─02 线程的启动和终止 │ │ 01 线程的启动一:继承Thread类.mp4 ...

    Java并发编程原理精讲 视频教程 下载 因为太大 百度网盘链接3.zip

    │ │ 01 认识线程.mp4 │ │ 02 线程和进程的关系.mp4 │ │ 03 并发和并行的区别.mp4 │ │ 04 并发编程的应用场景和风险.mp4 │ │ │ ├─02 线程的启动和终止 │ │ 01 线程的启动一:继承Thread类.mp4 ...

    Java并发编程原理精讲 视频教程 下载 因为太大 百度网盘链接1.zip

    │ │ 01 认识线程.mp4 │ │ 02 线程和进程的关系.mp4 │ │ 03 并发和并行的区别.mp4 │ │ 04 并发编程的应用场景和风险.mp4 │ │ │ ├─02 线程的启动和终止 │ │ 01 线程的启动一:继承Thread类.mp4 ...

    Java并发编程原理精讲 视频教程 下载 因为太大 百度网盘链接2.zip

    │ │ 01 认识线程.mp4 │ │ 02 线程和进程的关系.mp4 │ │ 03 并发和并行的区别.mp4 │ │ 04 并发编程的应用场景和风险.mp4 │ │ │ ├─02 线程的启动和终止 │ │ 01 线程的启动一:继承Thread类.mp4 ...

    Java并发编程原理与实战

    ThreadLocal 使用及实现原理.mp4 并发工具类CountDownLatch详解.mp4 并发工具类CyclicBarrier 详解.mp4 并发工具类Semaphore详解.mp4 并发工具类Exchanger详解.mp4 CountDownLatch,CyclicBarrier,Semaphore源码解析....

    java程序员-面试技巧

    - **并发编程**:了解线程的创建、同步机制,熟悉synchronized、volatile、ThreadLocal等概念。 - **集合框架**:深入理解List、Set、Map接口及其实现类,如ArrayList、LinkedList、HashMap、TreeMap等的内部工作...

    校园招聘面试心得与总结.docx

    - 认识到找工作是一种缘分和概率事件,即使基础扎实也可能面临挫折,但多尝试可以提高成功率。 - 不要害怕面试,失败是成功的垫脚石,多面试可以提升面试技巧。 - 保持积极态度,对工作要有追求,理解读书的意义...

    java-concurrent-source:Java多并发编程从入门到精通源码-源码通

    并发运算1.10 Linux和Windows对于并发采取的不同机制第2章认识Java里面的线程2.1先来看一下线程的简单实现三种方法2.2线程里面的属性和方法2.3关于线程的中断机制2.4螺纹的生命周期2.5什么是守护线程2.6线程组2.7...

    java 反模式 卷1 pdf

    书中的反模式是通过对实践中出现问题的总结,提炼出的不良设计或编程习惯,通过分析这些反模式,我们可以更清晰地认识到问题的本质,从而采取正确的解决方案。 Java反模式涵盖了许多主题,包括对象设计、类设计、...

    java多线程编程实例_Source

    实例中可能涵盖常见线程安全问题,如数据竞争、死锁、活锁和饥饿,以及如何通过使用volatile、Atomic变量、线程局部变量(ThreadLocal)等手段来解决这些问题。 六、线程中断与异常处理 Java的Thread类提供了...

    Spring.3.x企业应用开发实战(完整版).part2

    1.1 认识Spring 1.2 关于SpringSource 1.3 Spring带给我们什么 1.4 Spring体系结构 1.5 Spring 3.0的新功能 1.5.1 核心API更新到Java 5. 1.5.2 Spring表达式语言 1.5.3 可通过Java类提供IoC配置信息 1.5.4 通用类型...

    Spring3.x企业应用开发实战(完整版) part1

    1.1 认识Spring 1.2 关于SpringSource 1.3 Spring带给我们什么 1.4 Spring体系结构 1.5 Spring 3.0的新功能 1.5.1 核心API更新到Java 5. 1.5.2 Spring表达式语言 1.5.3 可通过Java类提供IoC配置信息 1.5.4 通用类型...

    6面阿里 P6+面经1

    2. **并发编程**:面试中涉及到的并发知识包括ThreadLocal、可迭代ThreadLocals、锁(如synchronized、Lock)、并发容器(如HashMap在1.7和1.8的实现差异、AQS-AbstractQueuedSynchronizer)以及同步工具类(如...

    马士兵 高并发 java架构 预习课 源码

    源码中可能涉及volatile、final以及ThreadLocal等特性,帮助我们理解JMM的工作原理和并发设计的底层逻辑。 通过对马士兵老师的高并发Java架构预习课程源码的深入学习,我们可以掌握多线程编程的核心技能,理解并发...

    scjp模拟试题三套

    9. **JVM**:对Java虚拟机(JVM)有基本认识,包括类加载机制、内存模型(堆、栈、方法区等)以及字节码的执行过程。 10. **编译与运行**:理解Java的编译和运行过程,包括javac命令、java命令,以及如何设置环境...

Global site tag (gtag.js) - Google Analytics