ThreadLocal的学习

1.ThreadLocal

           ThreadLocal并不是一个Thread,而是Thread的局部变量

         ThreadLocal类,它主要由四个方法组成initialValue(),get(),set(T),remove(),其中值得注意的是initialValue(),该方法是一个protected的方法,显然是为了子类重写而特意实现的。该方法返回当前线程在该线程局部变量的初始值,这个方法是一个延迟调用方法,在一个线程第1次调用get()时才执行,并且仅执行1次(即:最多在每次访问线程来获得每个线程局部变量时调用此方法一次,即线程第一次使用get()方法访问变量的时候。如果线程先于get方法调用set(T)方法,则不会在线程中再调用initialValue方法)。ThreadLocal中的缺省实现直接返回一个null

        在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。ThreadLocal的原理:

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);
        }
        values.put(Thread.currentThread(), newValue);
        return o ;
    }
    public Object initialValue() {
        return null;
    }
}
        在Java的多线程编程中,为保证多个线程对共享变量的安全访问,通常会使用synchronized来保证同一时刻只有一个线程对共享变量进行操作。这种情况下可以将类变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象。

2.ThreadLocal提供的四个方法

protected T initialValue()
       返回the initial value for this thread-local

public T get()
        返回the current thread's value of this thread-local

public void set(T value)
       设置the current thread's copy of this thread-local variable to the specified value. Most subclasses will have no need to override this method, relying solely on the initialValue() method to set the values of thread-locals.

public void remove()
        Removes the current thread's value for this thread-local variable. If this thread-local variable is subsequently read by the current thread, its value will be reinitialized by invoking its initialValue() method, unless its value is set by the current thread in the interim. This may result in multiple invocations of the initialValue method in the current thread.

3.典型示例(应用场景)

1>Hiberante的Session 工具类HibernateUtil

     这个类是Hibernate官方文档中HibernateUtil类,用于session管理。

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();
    }
}
2>在微信开发中的应用

        缓冲微信开发中的第三方应用,也就是微信公众号.我们的需求是要求每一个公众号在并发执行的过程中能够被共享.一种方法是将该公众号放在缓存中,如当前使用较多的redis缓冲,每次在调用的时候都去redis缓冲中取该公众号,如果发现没有,去数据库中取出来,再将该公众号设置到缓存中.

      还可以在拦截器中初始化ThreadLocal的值,或者是在监听器中初始化ThreadLocal的值.

public class AppCache {
    private final String KEY_PREFIX = "app:";
    private static final ThreadLocal<App> localApp = new ThreadLocal<App>();

    @Autowired
    private CacheService<App> redisAppCache;
    @Autowired
    private AppService appService;

    public App fetchApp(String appId) {
        App app = redisAppCache.get(KEY_PREFIX + appId);
        if (app == null) {
            app = appService.findById(appId);
            if (app != null) {
                redisAppCache.set(KEY_PREFIX + appId, app);
            } else {
                String message = "Can not find the app from the database by id " + appId;
                throw new ServiceErrorException(message);
            }
        }
        localAccount.set(app);
        return app;
    }
    //刷新App
    public App refreshApp(App app) {
        redisAppCache.set(KEY_PREFIX + app.getId(), app);
        localApp.set(app);
        return app;
    }
    //删除App,也就是将缓冲中的app给删掉
    public void deleteApp(String appId) {
        redisAppCache.del(KEY_PREFIX + appId);
        localApp.set(null);
    }
    //在需要该App的时候,我们就可以调用该方法获取
    public static App getCurrentApp() {
        return localApp.get();
    }
}

3>Java的多线程编程中ThreadLocal的应用

       可参考百度百科.

4.ThreadLocal和线程同步机制的比较

        ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。

        在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。而ThreadLocal则从另一个角度来解决多线程的并发访问。在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。ThreadLocal中可以持有任何类型的对象.

        对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
        ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别。synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
        Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。当然ThreadLocal并不能替代synchronized,它们处理不同的问题域。Synchronized用于实现同步机制,比ThreadLocal更加复杂。 他们之间不能彼此替代,只是从不同的角度去解决线程访问资源的问题。threadLocal无法替代锁实现的资源共享,而锁也做不到可以提供给独立的线程实例资源。
         ThreadLocal使用场合主要解决多线程中数据数据因并发产生不一致问题。ThreadLocal为每个线程中的并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,单大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。
         ThreadLocal不能使用原子类型,只能使用Object类型。ThreadLocal的使用比synchronized要简单得多。
         同步机制利用所实现资源的同步访问,确保某一个时刻只有一个线程在访问资源;而ThreadLoca则规避了同步,让每一个线程有自己的一份副本。

5. 基于JVM的内存模型来分析ThreadLocal

        ThreadLocal本质上是一个类似Map的结构,以各个线程对象本身为Key,将其值存放进去。这个结构在所有的基于同一个线程类创建出来的线程中被共享所有,就是只有一个,单例的对象。  虽然,使用get()方法来读取其值,但是默认的是使用当前的thread对象做为Key来检索的。

        ThreadLocal存放的内容,不支持基本数据类型,只支持对象类型。用以存放线程私有的数据,以规避繁琐的同步机制下的资源共享。一般而言,少量数据是可以通过这种简便的方式而实现线程访问的。这些数据不涉及到线程之间的通信和共享问题。

6.相关资料查阅

        ThreadLocal  百度百科

        深入理解ThreadLocal:  http://my.oschina.net/clopopo/blog/149368

        深入研究ThreadLocal类:  http://lavasoft.blog.51cto.com/62575/51926/

       理解ThreadLocal:  http://blog.jobbole.com/20400/


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值