Java多线程之 ThreadLocal

来源:互联网 发布:js 一分钟倒计时代码 编辑:程序博客网 时间:2024/04/30 05:25

一、什么是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

0 0
原创粉丝点击