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