Java线程(篇外篇):线程本地变量ThreadLocal

       首先说明ThreadLocal存放的值是线程内共享的,线程间互斥的,主要用于线程内共享一些数据,避免通过参数来传递,这样处理后,能够优雅的解决一些实际问题,比如Hibernate中的OpenSessionInView,就是使用ThreadLocal保存Session对象,还有我们经常用ThreadLocal存放Connection,代码如:

/**
 * 数据库连接管理类
 * @author 爽
 *
 */
public class ConnectionManager {

	/** 线程内共享Connection,ThreadLocal通常是全局的,支持泛型 */
	private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
	
	public static Connection getCurrConnection() {
		// 获取当前线程内共享的Connection
		Connection conn = threadLocal.get();
		try {
			// 判断连接是否可用
			if(conn == null || conn.isClosed()) {
				// 创建新的Connection赋值给conn(略)
				// 保存Connection
				threadLocal.set(conn);
			}
		} catch (SQLException e) {
			// 异常处理
		}
		return conn;
	}
	
	/**
	 * 关闭当前数据库连接
	 */
	public static void close() {
		// 获取当前线程内共享的Connection
		Connection conn = threadLocal.get();
		try {
			// 判断是否已经关闭
			if(conn != null && !conn.isClosed()) {
				// 关闭资源
				conn.close();
				// 移除Connection
				threadLocal.remove();
				conn = null;
			}
		} catch (SQLException e) {
			// 异常处理
		}
	}
}

       这样处理的好处:

  1. 统一管理Connection;
  2. 不需要显示传参Connection,代码更优雅;
  3. 降低耦合性。

       ThreadLocal有四个方法,分别为:

initialValue

protected T initialValue()

返回此线程局部变量的当前线程的初始值。最多在每次访问线程来获得每个线程局部变量时调用此方法一次,即线程第一次使用 get() 方法访问变量的时候。如果线程先于 get 方法调用 set(T) 方法,则不会在线程中再调用 initialValue 方法。

该实现只返回 null;如果程序员希望将线程局部变量初始化为 null 以外的某个值,则必须为 ThreadLocal 创建子类,并重写此方法。通常,将使用匿名内部类。initialValue 的典型实现将调用一个适当的构造方法,并返回新构造的对象。

返回:返回此线程局部变量的初始值

get

public T get()

返回此线程局部变量的当前线程副本中的值。如果这是线程第一次调用该方法,则创建并初始化此副本。

返回:此线程局部变量的当前线程的值

set

public void set(T value)

将此线程局部变量的当前线程副本中的值设置为指定值。许多应用程序不需要这项功能,它们只依赖于 initialValue() 方法来设置线程局部变量的值。

参数:value - 存储在此线程局部变量的当前线程副本中的值。

remove

public void remove()

移除此线程局部变量的值。这可能有助于减少线程局部变量的存储需求。如果再次访问此线程局部变量,那么在默认情况下它将拥有其 initialValue。

       很多人对ThreadLocal存在一定的误解,说ThreadLocal中有一个全局的Map,set时执行map.put(Thread.currentThread(), value),get和remove时也同理,但SUN的大师们是否是如此实现的,我们只能去看源码了。

       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();
	// 获取当前线程本地变量Map
	ThreadLocalMap map = getMap(t);
	// map不为空
	if (map != null)
		// 存值
		map.set(this, value);
	else
		// 创建一个当前线程本地变量Map
		createMap(t, value);
}

/**
 * Get the map associated with a ThreadLocal. Overridden in
 * InheritableThreadLocal.
 *
 * @param  t the current thread
 * @return the map
 */
ThreadLocalMap getMap(Thread t) {
	// 获取当前线程的本地变量Map
	return t.threadLocals;
}

       这里注意,ThreadLocal中是有一个Map,但这个Map不是我们平时使用的Map,而是ThreadLocalMap,ThreadLocalMap是ThreadLocal的一个内部类,不对外使用的。当使用ThreadLocal存值时,首先是获取到当前线程对象,然后获取到当前线程本地变量Map,最后将当前使用的ThreadLocal和传入的值放到Map中,也就是说ThreadLocalMap中存的值是[ThreadLocal对象, 存放的值],这样做的好处是,每个线程都对应一个本地变量的Map,所以一个线程可以存在多个线程本地变量

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

       有了之前set方法的分析,get方法也同理,需要说明的是,如果没有进行过set操作,那从ThreadLocalMap中拿到的值就是null,这时get方法会返回初始值,也就是调用initialValue()方法,ThreadLocal中这个方法默认返回null。当我们有需要第一次get时就能得到一个值时,可以继承ThreadLocal,并且覆盖initialValue()方法。

       (完)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏everhad

Android内存泄漏分享

内容概述 内存泄漏和内存管理相关基础。 Android中的内存使用。 内存分析工具和实践。 以下内容不考虑非引用类型的数据,或者将其等同为对应的引用类型看待——...

2276
来自专栏Django Scrapy

python3的一些简单高级用法(未完待续)三元运算生成列表字典等if or 简化lambda 函数+filter 函数

三元运算 可以解决if else 行数过多问题 print(1 if 5>3 else 0) print('yes' if 3<1 else 'no') a =...

2977
来自专栏崔庆才的专栏

Scrapy-Redis分布式爬虫源码解析

Scrapy-Redis库已经为我们提供了Scrapy分布式的队列、调度器、去重等功能,其GitHub地址为:https://github.com/rmax/s...

4588
来自专栏西枫里博客

PHP7新特性之两个小小语法糖。

想起写下这篇原本是因为群里龙大佬说PHP7下count有问题,顺道就讽了他一句。其实我自己也没有详细了解下PHP7到底在哪些方面做了修改。所以空了就翻了翻手册,...

911
来自专栏猿天地

面试题之死锁解密

在多线程环境中,我们经常会遇到多个线程访问同一个共享资源的情况,这个时候必须考虑如何维护数据一致性,常见的方式是加锁处理。只有拿到锁的线程才可以访问共享资源,通...

1381
来自专栏Ryan Miao

java中使用junit测试

最初写代码只要功能走通就不管了,然后如果出了什么问题再去修改,这是因为没做测试的工作。测试其实很简单。 1.准备 当前使用idea编写代码,用maven构建工程...

3387
来自专栏帅小子的日常

redis的基本数据类型

2727
来自专栏Java面试通关手册

Java多线程学习(五)线程间通信知识点补充

我自己总结的Java学习的系统知识点以及面试问题,目前已经开源,会一直完善下去,欢迎建议和指导欢迎Star: https://github.com/Snailc...

1063
来自专栏小灰灰

Java并发之 volatile & synchronized & ThreadLocal 讲解

Java 之 volatile & synchronized & ThreadLocal 讲解 在并发编程中,基本上离不开这三个东西,如何实现多线程之间的数据...

2176
来自专栏LanceToBigData

ThreadLocal

一、定义   翻译成中文应该叫做线程局部变量。这个类到底有什么用处呢?在并发编程的时候,成员变量如果不做任何处理其实是线程不安全的,各个线程都在操作同一个变量,...

1906

扫码关注云+社区