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多线 ...
随机推荐
- 最简单的用jquery实现动画的跳到顶部和底部
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- Java中什么时候使用构造方法
JAVA是面向对象的语言,面向对象不是这么直接简单,它的设计思想就是要代码重用.即我以前干过类似的事,那么我找出以前可以用到的代码,完成一部分.以前没有的我重新写.这样就有了类.有了类,就是有了可以重 ...
- leetcode:Reverse Linked List II
Reverse a linked list from position m to n. Do it in-place and in one-pass. For example:Given 1-> ...
- getHitRect获取点击控件的位置
public void getHitRect(Rect outRect) Added in API level 1 Hit rectangle in parent ...
- sqlserver重命名字段名称
EXEC sp_rename 'S2BASE_PRODUCT.[PRODUCT_ID]','TABTYPE_ID','COLUMN';
- 连接Access数据库查询语句
--在使用之前注意将“生成”里的“配置管理器”的“配置平台”改成X86 private void button1_Click(object sender, EventArgs e)//查询 { Ole ...
- 用FireMonkey写QQ皮肤
这是运行在Windows平台的效果,同样不需要改一行代码就可以运行在Mac Os,并且效果完全相同: 用FireMonkey做界面速度非常快,其提供的Effect ,Filter,Animation等 ...
- memcached增删改查
1)add语法:add key flag expire byteskey 键flag 标志expire 过期时间,可以是秒或一个具体的时间戳bytes 要存的东西的bytes长度 PS:只能添加内存里 ...
- 数据结构——Java实现单向链表
结点类: /** * @author zhengbinMac * 一个OnelinkNode类的对象只表示链表中的一个结点,通过成员变量next的自引用方式实现线性表中各数据元素的逻辑关系. */ p ...
- 细数Android开源项目中那些频繁使用的并发库中的类
这篇blog旨在帮助大家 梳理一下前面分析的那些开源代码中喜欢使用的一些类,这对我们真正理解这些项目是有极大好处的,以后遇到类似问题 我们就可以自己模仿他们也写 出类似的代码. 1.ExecutorS ...