SimpleDateFormat
SimpleDateFormat是非线程安全,测试Demo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
| 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 ); } } } } |
输出结果:
1
| today= 2014 - 10 - 18 |
由于创建一个 SimpleDateFormat 实例的开销比较昂贵,解析字符串时间时频繁创建生命周期短暂的实例导致性能低下。所以将 SimpleDateFormat 定义为静态类变量。但是,但是 SimpleDateFormat 是非线程安全的。如果用 synchronized 线程同步同样面临问题,同步导致性能下降。
使用 Threadlocal 解决了此问题,对于每个线程 SimpleDateFormat 不存在影响他们之间协作的状态,为每个线程创建一个 SimpleDateFormat 变量的拷贝或者叫做副本。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| 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 的定义:
1
| ThreadLocal.ThreadLocalMap threadLocals = null ; |
从这个函数中可以看出来,Thread 中的 threadlocals 变量是在 ThreadLocal 对象中调用 createMap 函数来初始化的。
下面是 ThreadLocal 截取的部分源码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
| 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); } } } |
在 ThreadLocal 的 set 函数中,可以看到,其中的 map.set(this, value); 把当前的 threadlocal 传入到 map 中作为键,也就是说,在不同的线程的 threadlocals 变量中,都会有一个以你所声明的那个线程局部变量 threadlocal 作为键的 key-value。假设说声明了 N 个这样线程的局部变量,那么在线程的 ThreadLocalMap 中就会有 n 个分别以你线程的局部变量作为 key 的键值对。
转载请并标注: “本文转载自 linkedkeeper.com (文/张松然)”