我是
靠谱客的博主
正直花瓣,最近开发中收集的这篇文章主要介绍
ThreadLocal深入理解,觉得挺不错的,现在分享给大家,希望可以做个参考。
概述
首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。
另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map,执行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。
java代码:
- private static final ThreadLocal threadSession = new ThreadLocal();
-
- public static Session getSession() throws InfrastructureException {
- Session s = (Session) threadSession.get();
- try {
- if (s == null) {
- s = getSessionFactory().openSession();
- threadSession.set(s);
- }
- } catch (HibernateException ex) {
- throw new InfrastructureException(ex);
- }
- return s;
- }
可以看到在getSession()方法中,首先判断当前线程中有没有放进去session,如果还没有,那么通过sessionFactory().openSession()来创建一个session,再将session set到线程中,实际是放到当前线程的ThreadLocalMap这个map中,这时,对于这个session的唯一引用就是当前线程中的那个ThreadLocalMap(下面会讲到),而threadSession作为这个值的key,要取得这个session可以通过threadSession.get()来得到,里面执行的操作实际是先取得当前线程中的ThreadLocalMap,然后将threadSession作为key将对应的值取出。这个session相当于线程的私有变量,而不是public的。
总之,ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。归纳了两点:
ThreadLocal的应用场合,我觉得最适合的是按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。
可以看到ThreadLocal类中的变量只有这3个int型:
java代码:
- private final int threadLocalHashCode = nextHashCode();
- private static int nextHashCode = 0;
- private static final int HASH_INCREMENT = 0x61c88647;
而作为ThreadLocal实例的变量只有 threadLocalHashCode 这一个,nextHashCode 和HASH_INCREMENT 是ThreadLocal类的静态变量,实际上HASH_INCREMENT是一个常量,表示了连续分配的两个ThreadLocal实例的threadLocalHashCode值的增量,而nextHashCode 的表示了即将分配的下一个ThreadLocal实例的threadLocalHashCode 的值。
java代码:
- private final int threadLocalHashCode = nextHashCode();
那么nextHashCode()做了什么呢:
java代码:
- private static synchronized int nextHashCode() {
- int h = nextHashCode;
- nextHashCode = h + HASH_INCREMENT;
- return h;
- }
就是将ThreadLocal类的下一个hashCode值即nextHashCode的值赋给实例的threadLocalHashCode,然后nextHashCode的值增加HASH_INCREMENT这个值。
java代码:
- ThreadLocalMap map = Thread.currentThread().threadLocals;
这个ThreadLocalMap 类是ThreadLocal中定义的内部类,但是它的实例却用在Thread类中:
java代码:
- public class Thread implements Runnable {
- ......
-
-
-
- ThreadLocal.ThreadLocalMap threadLocals = null;
- ......
- }
再看这句:
java代码:
- if (map != null)
- map.set(this, value);
也就是将该ThreadLocal实例作为key,要保持的对象作为值,设置到当前线程的ThreadLocalMap 中,get()方法同样大家看了代码也就明白了,ThreadLocalMap 类的代码太多了,我就不帖了,自己去看源码吧。
1.区别ThreadLocal 与 synchronized
- ThreadLocal是一个线程隔离(或者说是线程安全)的变量存储的管理实体(注意:不是存储用的),它以Java类方式表现;
- synchronized是Java的一个保留字,只是一个代码标识符,它依靠JVM的锁机制来实现临界区的函数、变量在CPU运行访问中的原子性。
两者的性质、表现及设计初衷不同,因此没有可比较性。
2.理解ThreadLocal中提到的变量副本
java代码:
- public void set(T value) {
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null)
- map.set(this, value);
- else
- createMap(t, value);
- }
-
- ThreadLocalMap getMap(Thread t) {
- return t.threadLocals;
- }
(有兴趣的朋友可以阅读Java的ThreadLocal源码)因此,我们可以知道,所谓的变量副本,即是对Object Reference(对象引用)的拷贝。
3.理解Thread和 ThreadLocal对变量的引用关系
-
-
我们知道一个ThreadOne的执行会贯穿多个方法MethodA、MethodB、MethodC这些方法可能分布于不同的类实例。假设,这些方法分别使用了ThreadLocalA、ThreadLocalB、ThreadLocalC来保存线程本地变量,那么这些变量都存于ThreadOne的Map中,并使用各自的ThreadLocal实例作为key。 因此,可以认为,借助ThreanLocal的set方法,在X轴上,Thread横向关联同一线程上下文中来自多个Method的变量引用副本。
- 接着说ThreadLocal。 一个MethodA中的X变量将被多个线程ThreadOne、ThreadTwo、ThreadThree所访问。假设MethodA使用ThreadLocal存储X,通过set方法,以ThreadLocal作为key值,将不同线程来访时的不同的变量值引用保存于ThreadOne、ThreadTwo、ThreadThree的各自线程上下文中,确保每个线程有自己的一个变量值。因此,可以认为,ThreadLocal是以Method为Y轴,纵向关联了处于同一方法中的不同线程上的变量。
示例
本文借花献佛,引用Tim Cull的博文“SimpleDateFormat: Performance Pig”介绍下ThreadLocal的简单使用,同时也对SimpleDateFormat的使用有个深入的了解。
Tim Cull 写道
Just yesterday I came across this problem “in the wild” for the third time in my career so far: an application with performance problems creating tons of java.text.SimpleDateFormat instances. So, I have to get this out there: creating a new instance of SimpleDateFormat is incredibly expensive and should be minimized. In the case that prompted this post, I was using JProfiler to profile this code that parses a CSV file and discovered that 50% of the time it took to suck in the file and make 55,000 objects out of it was spent solely in the constructor of SimpleDateFormat. It created and then threw away a new one every time it had to parse a date. Whew!
大致意思:Tim Cull碰到一个SimpleDateFormat带来的严重的性能问题,该问题主要有SimpleDateFormat引发,创建一个SimpleDateFormat实例的开销比较昂贵,解析字符串时间时频繁创建生命周期短暂的实例导致性能低下。即使将SimpleDateFormat定义为静态类变量,貌似能解决这个问题,但是SimpleDateFormat是非线程安全的,同样存在问题,如果用‘synchronized’线程同步同样面临问题,同步导致性能下降(线程之间序列化的获取SimpleDateFormat实例)。
Tim Cull使用Threadlocal解决了此问题,对于每个线程SimpleDateFormat不存在影响他们之间协作的状态,为每个线程创建一个SimpleDateFormat变量的拷贝或者叫做副本,代码如下:
java代码:
- import java.text.DateFormat;
- import java.text.ParseException;
- import java.text.SimpleDateFormat;
- import java.util.Date;
-
-
-
-
-
- public class DateUtil {
-
- private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
-
- @SuppressWarnings("rawtypes")
- private static ThreadLocal threadLocal = new ThreadLocal() {
- protected synchronized Object initialValue() {
- return new SimpleDateFormat(DATE_FORMAT);
- }
- };
-
- public static DateFormat getDateFormat() {
- return (DateFormat) threadLocal.get();
- }
-
- public static Date parse(String textDate) throws ParseException {
- return getDateFormat().parse(textDate);
- }
- }
创建一个ThreadLocal类变量,这里创建时用了一个匿名类,覆盖了initialValue方法,主要作用是创建时初始化实例。也可以采用下面方式创建;
java代码:
-
- private static ThreadLocal threadLocal = new ThreadLocal();
-
- public static DateFormat getDateFormat()
- {
- DateFormat df = (DateFormat) threadLocal.get();
- if(df==null){
- df = new SimpleDateFormat(DATE_FORMAT)
- threadLocal.set(df);
- }
- return df;
- }
我们看下我们覆盖的initialValue方法:
java代码:
- protected T initialValue() {
- return null;
- }
最后
以上就是正直花瓣为你收集整理的ThreadLocal深入理解的全部内容,希望文章能够帮你解决ThreadLocal深入理解所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复