zoukankan      html  css  js  c++  java
  • Java多线程之 ThreadLocal

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

  • 相关阅读:
    整理—类型转换
    HTML简历
    数组
    选择语句2
    类型转换、运算符'
    C#(VS)基础
    hdu_1037(水题水疯了。。。史上最水)
    hdu_1033(我怎么找到的这么水的题,只为保存代码。。。)
    hdu_1030(数学题+找规律)
    hdu_1029_hash/map
  • 原文地址:https://www.cnblogs.com/lbangel/p/3186123.html
Copyright © 2011-2022 走看看