Java多线程之 ThreadLocal
一、什么是ThreadLocal?
顾名思义它是local variable(线程局部变量)。它的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。
使用场景
- To keep state with a thread (user-id, transaction-id, logging-id)
- To cache objects which you need frequently
二、ThreadLocal类
它主要由四个方法组成initialValue(),get(),set(T),remove(),其中值得注意的是initialValue(),该方法是一个protected的方法,显然是为了子类重写而特意实现的。该方法返回当前线程在该线程局部变量的初始值,这个方法是一个延迟调用方法,在一个线程第1次调用get()或者set(Object)时才执行,并且仅执行1次。
ThreadLocal的原理
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。比如下面的示例实现:
public class ThreadLocal
{
private Map values = Collections.synchronizedMap(new HashMap());
public Object get()
{
Thread curThread = Thread.currentThread();
Object o = values.get(curThread);
if (o == null && !values.containsKey(curThread))
{
o = initialValue();
values.put(curThread, o);
}
return o;
} public void set(Object newValue)
{
values.put(Thread.currentThread(), newValue);
} public Object initialValue()
{
return null;
}
}
ThreadLocal 的使用
使用方法一:
Hibernate的文档时看到了关于使ThreadLocal管理多线程访问的部分。具体代码如下
public class HibernateUtil {
private static Log log = LogFactory.getLog(HibernateUtil.class);
private static final SessionFactory sessionFactory; //定义SessionFactory static {
try {
// 通过默认配置文件hibernate.cfg.xml创建SessionFactory
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
log.error("初始化SessionFactory失败!", ex);
throw new ExceptionInInitializerError(ex);
}
} //创建线程局部变量session,用来保存Hibernate的Session
public static final ThreadLocal session = new ThreadLocal(); /**
* 获取当前线程中的Session
* @return Session
* @throws HibernateException
*/
public static Session currentSession() throws HibernateException {
Session s = (Session) session.get();
// 如果Session还没有打开,则新开一个Session
if (s == null) {
s = sessionFactory.openSession();
session.set(s); //将新开的Session保存到线程局部变量中
}
return s;
} public static void closeSession() throws HibernateException {
//获取线程局部变量,并强制转换为Session类型
Session s = (Session) session.get();
session.set(null);
if (s != null)
s.close();
}
}
在这个类中,由于没有重写ThreadLocal的initialValue()方法,则首次创建线程局部变量session其初始值为null,第一次调用currentSession()的时候,线程局部变量的get()方法也为null。因此,对session做了判断,如果为null,则新开一个Session,并保存到线程局部变量session中,这一步非常的关键,这也是“public static final ThreadLocal session = new ThreadLocal()”所创建对象session能强制转换为Hibernate Session对象的原因。
使用方法二
当要给线程初始化一个特殊值时,需要自己实现ThreadLocal的子类并重写该方法,通常使用一个内部匿名类对ThreadLocal进行子类化,EasyDBO中创建jdbc连接上下文就是这样做的:
public class JDBCContext{
private static Logger logger = Logger.getLogger(JDBCContext.class);
private DataSource ds;
protected Connection connection;
private boolean isValid = true;
private static ThreadLocal jdbcContext; private JDBCContext(DataSource ds){
this.ds = ds;
createConnection();
}
public static JDBCContext getJdbcContext(javax.sql.DataSource ds)
{
if(jdbcContext==null)jdbcContext=new JDBCContextThreadLocal(ds);
JDBCContext context = (JDBCContext) jdbcContext.get();
if (context == null) {
context = new JDBCContext(ds);
}
return context;
} private static class JDBCContextThreadLocal extends ThreadLocal {
public javax.sql.DataSource ds;
public JDBCContextThreadLocal(javax.sql.DataSource ds)
{
this.ds=ds;
}
protected synchronized Object initialValue() {
return new JDBCContext(ds);
}
}
}
使用单例模式,不同的线程调用getJdbcContext()获得自己的jdbcContext,都是通过JDBCContextThreadLocal 内置子类来获得JDBCContext对象的线程局部变量。
下面是一个简单的例子:
package cn.itcast.test; import java.util.Random; public class ThreadLocalTest {
public static void main(String[] args) {
final A a = new A();
final B b = new B();
for(int i=0;i<5;i++){
new Thread(){
public void run(){
/*1. MyThreadLocalData.x.set(new Random().nextInt(10000));
System.out.println(Thread.currentThread() + "has put " + MyThreadLocalData.x.get());
a.say();
b.sayHello();*/ /*2. MyThreadLocalData.set(new Random().nextInt(10000));
System.out.println(Thread.currentThread() + "has put " + MyThreadLocalData.get());
a.say();
b.sayHello();*/ MyThreadLocalData.getMyData().setX(new Random().nextInt(10000));
System.out.println(Thread.currentThread() + "has put " + MyThreadLocalData.getMyData().getX());
a.say();
b.sayHello();
MyThreadLocalData.clear();
}
}.start();
} } } class MyThreadLocalData{
//1. public static ThreadLocal x = new ThreadLocal();
/*2. private static ThreadLocal x = new ThreadLocal();
public static void set(Object val){
x.set(val);
} public static Object get(){
return x.get();
}*/ private MyThreadLocalData(){}
private static ThreadLocal instanceContainer = new ThreadLocal();
public static MyThreadLocalData getMyData(){
MyThreadLocalData instance = (MyThreadLocalData)instanceContainer.get();
if(instance == null){
instance = new MyThreadLocalData();
instanceContainer.set(instance);
}
return instance;
}
public static void clear(){
instanceContainer.remove();
} private Integer x;
public void setX(Integer x){
this.x = x;
}
public Integer getX(){
return x;
} } class A{
public void say(){
//1. System.out.println(Thread.currentThread() + ": A has getted " + MyThreadLocalData.x.get());
//2. System.out.println(Thread.currentThread() + ": A has getted " + MyThreadLocalData.get());
System.out.println(Thread.currentThread() + ": A has getted " + MyThreadLocalData.getMyData().getX());
}
} class B{
public void sayHello(){
//1. System.out.println(Thread.currentThread() + ": B has getted " + MyThreadLocalData.x.get());
//2. System.out.println(Thread.currentThread() + ": B has getted " + MyThreadLocalData.get());
System.out.println(Thread.currentThread() + ": B has getted " + MyThreadLocalData.getMyData().getX()); }
}
运行结果,可以看出每一个线程获取的是那本身的值:
Thread[Thread-2,5,main]has put 6093
Thread[Thread-4,5,main]has put 2603
Thread[Thread-1,5,main]has put 7691
Thread[Thread-1,5,main]: A has getted 7691
Thread[Thread-3,5,main]has put 6593
Thread[Thread-1,5,main]: B has getted 7691
Thread[Thread-4,5,main]: A has getted 2603
Thread[Thread-4,5,main]: B has getted 2603
Thread[Thread-2,5,main]: A has getted 6093
Thread[Thread-0,5,main]has put 7901
Thread[Thread-2,5,main]: B has getted 6093
Thread[Thread-3,5,main]: A has getted 6593
Thread[Thread-3,5,main]: B has getted 6593
Thread[Thread-0,5,main]: A has getted 7901
Thread[Thread-0,5,main]: B has getted 7901
Java多线程之 ThreadLocal的更多相关文章
- Java多线程基础-ThreadLocal
感谢原文作者:Yuicon 原文链接:https://segmentfault.com/a/1190000016705955 序 在多线程环境下,访问非线程安全的变量时必须进行线程同步,例如使用 sy ...
- java 多线程(threadlocal)
package com.example; import java.util.Random; public class App { public static class MyRunnable1 imp ...
- java多线程学习-ThreadLocal
为了凑字,把oracle文档里介绍ThreadLocal抄过来 public class ThreadLocal<T> extends Object This class provides ...
- Java多线程:ThreadLocal
一.ThreadLocal基础知识 ThreadLocal是线程的一个本地化对象,或者说是局部变量.当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的 ...
- java 多线程 day06 threadLocal
import java.util.HashMap;import java.util.Map;import java.util.Random; /** * Created by chengtao on ...
- java 多线程 :ThreadLocal 共享变量多线程不同值方案;InheritableThreadLocal变量子线程中自定义值,孙线程可继承
ThreadLocal类的使用 变量值的共享可以使用public static变量的形式,所有的线程都是用同一个public static变量.如果想实现每一个线程都有自己的值.该变量可通过Thr ...
- java多线程--------深入分析 ThreadLocal 内存泄漏问题
前言 ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度.但是如果滥用ThreadLocal,就可能 ...
- java 多线程 一个博客
http://blog.csdn.net/a352193394/article/category/2563875 Java多线程之~~~线程安全容器的非阻塞容器 在并发编程中,会经常遇到使用容器.但是 ...
- Java多线程系列——从菜鸟到入门
持续更新系列. 参考自Java多线程系列目录(共43篇).<Java并发编程实战>.<实战Java高并发程序设计>.<Java并发编程的艺术>. 基础 Java多线 ...
随机推荐
- vc 6.0 连接数据库
在mysql的文档(C:\Program Files\MySQL\MySQL Server 5.5\lib)里面找到libmysql.dll和libmysql.lib 两个文件,将他们移到 工程文件下 ...
- ccnu-线段树联系-单点更新2-B
B - 单点更新2 Time Limit:3000MS Memory Limit:32768KB 64bit IO Format:%I64d & %I64u Submit Status Des ...
- [HDOJ5584]LCM Walk(数论,规律)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5584 给一个坐标(ex, ey),问是由哪几个点走过来的.走的规则是x或者y加上他们的最小公倍数lcm ...
- 《OD学hadoop》Hadoop前置
一.Hadoop 前置课程 1. Linux系统,基本命令 2. Java语言,JavaSE相关知识 3. MySQL基本的DML和DDL SQL on Hadoop
- HTML & CSS 小总结
1. web 主机代理商 web hosting company, 让他们的服务器为你的页面服务2. 选择网站名字 例如: www.1234.com3. 寻找 把文件从电脑传到主机的途径4. 把新网站 ...
- PopupWindwo和AlertDialog的区别
AlertDialog 是非阻塞式对话框:AlertDialog弹出时,后台还可以做事情:而PopupWindow是阻塞式对话框:PopupWindow弹出时, 程序会等 待,在PopupWind ...
- java注解Annotation
扯扯注解的蛋 为什么学习注解?学习注解有什么好处?学完能做什么? 1.能够读懂别人的代码,特别是框架相关的代码 2.让编程更加简洁,代码更加清晰 3.让别人高看你一眼 注解是java1.5引入的 概念 ...
- Java笔记之数组
1.int flags[] = new int[10];数组中的每个元素初始化为0. Arrays.fill(flags, 0);将数组中每个元素置为0.
- I.MX6 Linux mipi配置数据合成
/*************************************************************************** * I.MX6 Linux mipi配置数据合 ...
- 用 Xcode 开发 Cydia Substrate 插件(一)
关于这方面的中文资料太少了,以至于可能很多对插件开发感兴趣的孩子们都不知从何下手,于是呢我就写了这篇文章,希望对你能有所帮助.如果你觉得文章内容有什么错误呢也请提出来. 准备开发环境 1. 从 App ...