系列文章:• WeakReference和StrongReference SimpleDateFormat SimpleDateFormat是非线程安全,测试Demo: package com.jd.api.admin.common; import java.text.SimpleDateFormat; import java.util.Date; public class Test { public static void main(String[] args) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); Date today = new Date(); Date tomorrow = new Date(today.getTime() + 1000 * 60 * 60 * 24); System.out.println(today); // 今天是2014-10-17 System.out.println(tomorrow); // 明天是2014-10-18 Thread thread1 = new Thread(new Thread1(dateFormat, today)); thread1.start(); Thread thread2 = new Thread(new Thread2(dateFormat, tomorrow)); thread2.start(); } } class Thread1 implements Runnable { private SimpleDateFormat dateFormat; private Date date; public Thread1(SimpleDateFormat dateFormat, Date date) { this.dateFormat = dateFormat; this.date = date; } public void run() { for (; ; ) {// 一直循环到出问题为止吧。 String strDate = dateFormat.format(date); // 如果不等于2014-10-17,证明出现线程安全问题了!!!! if (!"2014-10-17".equals(strDate)) { System.err.println("today=" + strDate); System.exit(0); } } } } class Thread2 implements Runnable { private SimpleDateFormat dateFormat; private Date date; public Thread2(SimpleDateFormat dateFormat, Date date) { this.dateFormat = dateFormat; this.date = date; } public void run() { for (; ; ) { String strDate = dateFormat.format(date); // 如果不等于2014-10-18,证明出现线程安全问题了!!!! if (!"2014-10-18".equals(strDate)) { System.err.println("tomorrow=" + strDate); System.exit(0); } } } } 输出结果: today=2014-10-18 由于创建一个 SimpleDateFormat 实例的开销比较昂贵,解析字符串时间时频繁创建生命周期短暂的实例导致性能低下。所以将 SimpleDateFormat 定义为静态类变量。但是,但是 SimpleDateFormat 是非线程安全的。如果用 synchronized 线程同步同样面临问题,同步导致性能下降。 使用 Threadlocal 解决了此问题,对于每个线程 SimpleDateFormat 不存在影响他们之间协作的状态,为每个线程创建一个 SimpleDateFormat 变量的拷贝或者叫做副本。 import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; /** * 使用ThreadLocal以空间换时间解决SimpleDateFormat线程安全问题。 */ public class DateFormatUtils { // 创建一个ThreadLocal类变量,这里创建时用了一个匿名类,覆盖了initialValue方法,主要作用是创建时初始化实例。 private static final ThreadLocal threadLocal = new ThreadLocal() { protected synchronized Object initialValue() { return new SimpleDateFormat(); } }; // 获取线程的变量副本,如果不覆盖initialValue,第一次get返回null,故需要初始化一个SimpleDateFormat,并set到threadLocal中 public static DateFormat getDateFormat(String pattern) { DateFormat dateFormat = (DateFormat) threadLocal.get(); if (dateFormat == null) { dateFormat = new SimpleDateFormat(pattern); threadLocal.set(dateFormat); } return dateFormat; } public static Date parse(String pattern, String textDate) throws ParseException { return getDateFormat(pattern).parse(textDate); } public static String format(String pattern, Date date) { return getDateFormat(pattern).format(date); } } ThreadLocal 谈到 ThreadLocal 的使用,我们先来了解一下 ThreadLocal 是什么?ThreadLocal 是在 JDK1.2 的版本中开始提供的,他不是一个线程,而是一个线程的本地化对象。当某个变量在使用 ThreadLocal 进行维护时,ThreadLocal 为使用该变量的每个线程分配了一个独立的变量副本,每个线程可以自行操作自己对应的变量副本,而不会影响其他线程的变量副本。 研究ThreadLocal的最根本的实现原理,一共有三个问题。
ThreadLocal 是怎么实现了多个线程之间每个线程一个变量副本的?它是如何实现共享变量的。 ThreadLocal 提供了 set 和 get 访问器用来访问与当前线程相关联的线程局部变量。可以从 ThreadLocal 的 get 函数中看出来,其中 getmap 函数是用 t 作为参数,这里 t 就是当前执行的线程。 从而得知,get 函数就是从当前线程的 threadlocalmap 中取出当前线程对应的变量的副本(注意,变量是保存在线程中的,而不是保存在 ThreadLocal 变量中)。当前线程中,有一个变量引用名字是 threadLocals,这个引用是在 ThreadLocal 类中 createmap 函数内初始化的。 每个线程都有一个这样的 threadLocals 引用的 ThreadLocalMap,以 ThreadLocal 和 ThreadLocal 对象声明的变量类型作为参数。这样,我们所使用的 ThreadLocal 变量的实际数据,通过 get 函数取值的时候,就是通过取出 Thread 中 threadLocals 引用的 map,然后从这个 map 中根据当前 threadLocal 作为参数,取出数据。现在,变量的副本从哪里取出来的已经确认解决了。 ThreadLocal 整体感觉就是,一个包装类。声明了这个类的对象之后,每个线程的数据其实还是在自己线程内部通过 threadLocals 引用到的自己的数据。只是通过 ThreadLocal 访问这个数据而已 看下面 set 函数。当线程中的 threadlocalmap 是 null 的时候,会调用 createmap 创建一个 map。同时根据函数参数设置上初始值。也就是说,当前线程的 threadlocalmap 是在第一次调用 set 的时候创建 map 并且设置上相应的值的。 在代码中声明的ThreadLocal对象,实际上只有一个。在每个线程中,都维护了一个 threadlocals 对象,在没有 ThreadLocal 变量的时候是 null 的。一旦在 ThreadLocal 的 createMap 函数中初始化之后,这个 threadlocals 就初始化了。以后每次那个 ThreadLocal 对象想要访问变量的时候,比如 set 函数和 get 函数,都是先通过 getMap(t) 函数,先将线程的 map 取出,然后再从这个在线程(Thread)中维护的map中取出数据(以当前threadlocal作为参数)。 看 Thread 中 threadlocals 的定义: ThreadLocal.ThreadLocalMap threadLocals = null; 从这个函数中可以看出来,Thread 中的 threadlocals 变量是在 ThreadLocal 对象中调用 createMap 函数来初始化的。 下面是 ThreadLocal 截取的部分源码。 public class ThreadLocal<T> { protected T initialValue() { return null; } public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); } private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } 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; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal> { Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } } private static final int INITIAL_CAPACITY = 16; private Entry[] table; ThreadLocalMap(ThreadLocal firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); } } } 3. 不同的线程局部变量,比如说声明了 n 个 (n>=2) 这样的线程局部变量 threadlocal,那么在 Thread 中的 threadlocals 中是怎么存储的呢?threadlocalmap 中是怎么操作的? 在 ThreadLocal 的 set 函数中,可以看到,其中的 map.set(this, value); 把当前的 threadlocal 传入到 map 中作为键,也就是说,在不同的线程的 threadlocals 变量中,都会有一个以你所声明的那个线程局部变量 threadlocal 作为键的 key-value。假设说声明了 N 个这样线程的局部变量,那么在线程的 ThreadLocalMap 中就会有 n 个分别以你线程的局部变量作为 key 的键值对。 转载请并标注: “本文转载自 linkedkeeper.com ” ©著作权归作者所有 |