一、什么是ThreadLocal?

顾名思义它是local variable(线程局部变量)。它的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。

通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。
 
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。
 
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

使用场景

  1. To keep state with a thread (user-id, transaction-id, logging-id)
  2. To cache objects which you need frequently

二、ThreadLocal类

ThreadLocal()
          创建一个线程本地变量。
 
T get()
          返回此线程局部变量的当前线程副本中的值,如果这是线程第一次调用该方法,则创建并初始化此副本。
 
protected  T initialValue()
          返回此线程局部变量的当前线程的初始值。最多在每次访问线程来获得每个线程局部变量时调用此方法一次,即线程第一次使用 get() 方法访问变量的时候。如果线程先于 get 方法调用 set(T) 方法,则不会在线程中再调用 initialValue 方法。
 
   若该实现只返回 null;如果程序员希望将线程局部变量初始化为 null 以外的某个值,则必须为 ThreadLocal 创建子类,并重写此方法。通常,将使用匿名内部类。initialValue 的典型实现将调用一个适当的构造方法,并返回新构造的对象。
 
void remove()
          移除此线程局部变量的值。这可能有助于减少线程局部变量的存储需求。如果再次访问此线程局部变量,那么在默认情况下它将拥有其 initialValue。
 
void set(T value)
          将此线程局部变量的当前线程副本中的值设置为指定值。许多应用程序不需要这项功能,它们只依赖于 initialValue() 方法来设置线程局部变量的值。
 
在程序中一般都重写initialValue方法,以给定一个特定的初始值。

它主要由四个方法组成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的更多相关文章

  1. Java多线程基础-ThreadLocal

    感谢原文作者:Yuicon 原文链接:https://segmentfault.com/a/1190000016705955 序 在多线程环境下,访问非线程安全的变量时必须进行线程同步,例如使用 sy ...

  2. java 多线程(threadlocal)

    package com.example; import java.util.Random; public class App { public static class MyRunnable1 imp ...

  3. java多线程学习-ThreadLocal

    为了凑字,把oracle文档里介绍ThreadLocal抄过来 public class ThreadLocal<T> extends Object This class provides ...

  4. Java多线程:ThreadLocal

    一.ThreadLocal基础知识 ThreadLocal是线程的一个本地化对象,或者说是局部变量.当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的 ...

  5. java 多线程 day06 threadLocal

    import java.util.HashMap;import java.util.Map;import java.util.Random; /** * Created by chengtao on ...

  6. java 多线程 :ThreadLocal 共享变量多线程不同值方案;InheritableThreadLocal变量子线程中自定义值,孙线程可继承

      ThreadLocal类的使用 变量值的共享可以使用public static变量的形式,所有的线程都是用同一个public static变量.如果想实现每一个线程都有自己的值.该变量可通过Thr ...

  7. java多线程--------深入分析 ThreadLocal 内存泄漏问题

    前言 ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度.但是如果滥用ThreadLocal,就可能 ...

  8. java 多线程 一个博客

    http://blog.csdn.net/a352193394/article/category/2563875 Java多线程之~~~线程安全容器的非阻塞容器 在并发编程中,会经常遇到使用容器.但是 ...

  9. Java多线程系列——从菜鸟到入门

    持续更新系列. 参考自Java多线程系列目录(共43篇).<Java并发编程实战>.<实战Java高并发程序设计>.<Java并发编程的艺术>. 基础 Java多线 ...

随机推荐

  1. 虚拟机 主机无法访问虚拟机中Linux上的tomcat服务

    在wmware中安装linux后安装好数据库,JDK及tomcat后启动服务,虚拟机中可以访问,但是主机却无法访问,但是同时主机和虚拟机之间可以ping的通,网上查阅资料后,解决方法是关闭虚拟机中的防 ...

  2. php面向对象(一) 初窥

    初窥php面向对象 1.类:类就是属性和方法的集合 是一个抽象的概念比如生活中的“人”,"汽车"2.对象:对象是具体的事物 比如一叫“小强”的人 一辆叫“奥迪a7”的汽车3.类和对 ...

  3. VS2015中添加新建项,找不到ado .net entity datamodel的解决方法

    http://stackoverflow.com/questions/23046081/missing-ado-net-entity-data-model-on-visual-studio-2013 ...

  4. mysql 字符串函数

    对于针对字符串位置的操作,第一个位置被标记为1. ASCII(str) 返回字符串str的 最左面字符的ASCII代码值.如果str是空字符串, 返回0.如果str是NULL,返回NULL. mysq ...

  5. 巧用ifstream判断文件是否存在

    最近在写手写数字的识别软件,训练样例数量巨大而且数字个数不唯一,有可能在中途粘出一部分做测试样例.因此写下面的脚本来获取文件名,之后丢到训练函数里. #include <algorithm> ...

  6. pl/sql programming 05 循环迭代处理

    使用循环应考虑的因素 1. 循环什么时候结束 2. 什么时候测试是否该结束循环 3. 采用这种循环的原因 1. 普通循环(简单循环) 使用场合, 不能确定循环执行多少次, 要求循环至少执行一次. 另外 ...

  7. HeadFirst Jsp 07 (使用 jsp)

    Jsp 变成 Servlet, 容器会查看你的JSP, 把它转换成java源代码, 再编译成完整的Java servlet类. Jsp 不需要你的编译, 容器会自动替换成servlet. 在 jsp中 ...

  8. 用AngularJS开发下一代Web应用 系列入门基础教程

    开篇介绍 AngularJS是什么东西?我觉得不用再描述了.可自行去充电一下.按照惯例,让我们先看看一个Hello World的开门简介吧. <!doctype html> <htm ...

  9. Android 最火的快速开发框架XUtils

    参考:http://www.oschina.net/p/xutils 项目git地址https://github.com/wyouflf/xUtils 目录(?)[-] 最近搜了一些框架供初学者学习比 ...

  10. UVa 575 Skew Binary 歪斜二进制

    呵呵,这个翻译还是很直白的嘛,大家意会就好. 第一次看到这个高大上题目还是有点小害怕的,还好题没有做过深的文章. 只要按照规则转化成十进制就好了,而且题目本身也说了最大不超过一个int的范围(2^31 ...