一、什么是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. 【分享】Maven插件的源码下载(SVN)

    偶然的情况下找到了Maven插件源码的网址,现分享下 http://svn.apache.org/repos/asf/maven/plugins/ 可以使用SVN下载,在添加新的资源路径时,把上面的网 ...

  2. sysfs实例

    转自:http://blog.chinaunix.net/u1/51562/showart_1076295.html 一:前言 在设备模型中,sysfs文件系统用来表示设备的结构.将设备的层次结构形象 ...

  3. curl 查看网站连接情况

    curl -o /dev/null -s -w "nslookup_time :%{time_namelookup}\n time_connect: %{time_connect}\ntim ...

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

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

  5. tune 06 Database Configuration and I/O Issues

    1. 尽量读内存中的数据 2. 尽量减少IO矛盾, 即多个任务同时读写一块磁盘 表和索引要尽量分开在不同磁盘, 因为表和它的索引是同时读取的, 所以分磁盘后, 对性能会提高. 物理磁盘的调优相关 re ...

  6. 投影纹理映射(Projective Texture Mapping)

    摘抄“GPU Programming And Cg Language Primer 1rd Edition” 中文名“GPU编程与CG语言之阳春白雪下里巴人”  投影纹理映射( Projective ...

  7. Oracle数据库之二

    SELECT查询 函数分为: 单行函数 -- 一条记录进入,一条记录输出 多行函数(分组函数)-- 多条记录进入,按组输出 单行函数: select id,first_name,nvl(commiss ...

  8. Codeforces Round #272 (Div. 2) C. Dreamoon and Sums (数学 思维)

    题目链接 这个题取模的时候挺坑的!!! 题意:div(x , b) / mod(x , b) = k( 1 <= k <= a).求x的和 分析: 我们知道mod(x % b)的取值范围为 ...

  9. JavaScript——关于字符串的replace函数中的function函数的参数

    <!DOCTYPE> <html> <head> </head> <body> <script type="text/jav ...

  10. iOS 9的新内容

    https://www.hackingwithswift.com/ios9 Search extensibility Update: I wrote a tutorial on Core Spotli ...