以SimpleDateFormat解析日期为例进行说明
package org.world.traveler.common;
import java.text.SimpleDateFormat;
import java.util.Date;
public final class ThreadLocalTest {
private final static SimpleDateFormat SDF = (SimpleDateFormat) SimpleDateFormat.getDateTimeInstance();
private ThreadLocalTest() {
assert false;
}
public static void main(String[] args) {
test();
}
public static void test(){
final String[] dateArray = new String[]{"1991-01-01 11:11:11",
"1992-02-02 22:22:22",
"1993-03-03 13:33:33"};
for(int i = 0; i < dateArray.length; i++){
final int fianlVarI = i;
new Thread(()->{
try {
Date date = SDF.parse(dateArray[fianlVarI]);
String dateStr = SDF.format(date);
System.out.println("【i:" + fianlVarI
+"】:{ThreadId=" + Thread.currentThread().getId()
+ "; Date=" + date
+ "; dateStr=" + dateStr + "}");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
控制台打印
java.lang.NumberFormatException: multiple points
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.lang.Double.parseDouble(Double.java:538)
at java.text.DigitList.getDouble(DigitList.java:169)
at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at org.world.traveler.common.ThreadLocalTest.lambda$0(ThreadLocalTest.java:27)
at java.lang.Thread.run(Thread.java:745)
......
【i:1】:{ThreadId=9; Date=Fri Feb 02 22:22:22 CST 1319; dateStr=1319-2-2 22:22:22}
原因在于SimpleDateFormat对象本身是不安全,内部存在一个Calendar对象,这个对象是可以被共享的,当多个线程同时进行parse或format时(过程中会操作calendar对象) 就会出现并发修改的问题。
基于上述原因,在JDK8以前解决方案一般分两种:第一种将SimpleDateFormat定义成方法内的局部变量; 第二种是将其定义成线程本地变量。第一种方式的缺陷在于每次执行都会重新构造一个对象,执行次数越多造成的空间与时间的消耗就越多,而且受限于方法的定义范围。
ThreadLocal(线程局部变量 或 线程本地变量)
- 每个Thread都维护着一个自己的ThreadLocakMap对象(弱引用), 这个map的Key就是一个个ThreadLocal实例。 通常甚至JDK文档中说的threadLocal的值并不是指threadLocal变量本身的地址值,而是指它对应线程中这个map中的值。
- ThreadLocal变量的定义应该是private static final的,如果不是static那么使用ThreadLocal就失去了意义(和上面的第一种方式就没区别了),且这个变量本身不应该被某个线程所修改,所以一般加上final
- threadLocal.set(obj) (或初始方法)并不会复制或克隆一份obj,而仅仅是把threadLocal对象本身和obj作为Key/Value存储到当前线程的map中。threadLocal.get()也仅仅是从当前线程的map中取出threadLocal本身对应的值而已。所以使用threadLocal.initialValue()返回的对象(或set的对象)时不应该使用一个共享对象,否则就失去了使用ThreadLocal的意义,并且会产生并发问题。
所以ThreadLocal能使得各线程能够保持各自独立的一个对象(作为key的threalLocal本身是共享的,但value不是共享的),这解决的并不是共享变量并发更新的问题(value不是共享的,网上一大堆说ThreadLocal解决了共享变量并发问题的都是不正确的),它更不会复制或克隆变量到每个新的线程中,它的存在只是为了让线程 能拥有并方便的处理 线程本身的状态( 说白了就是方便线程本身 定义及操作 线程范围内的变量)。
根据对ThreadLocal的了解,修改上方的示例代码,用线程局部变量ThreadLocal<SimpleDateFormat> tl 替换SimpleDateFormat变量,解析时用tl.get()获取当前线程中tl对应的值,测试正常
package org.world.traveler.common;
import java.text.SimpleDateFormat;
import java.util.Date;
public final class ThreadLocalTest {
private ThreadLocalTest() {
assert false;
}
private final static ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return (SimpleDateFormat) SimpleDateFormat.getDateTimeInstance();
}
};
public static void main(String[] args) throws ClassNotFoundException {
test();
}
public static void test() {
final String[] dateArray = new String[] { "1991-01-01 11:11:11",
"1992-02-02 22:22:22", "1993-03-03 13:33:33" };
for (int i = 0; i < dateArray.length; i++) {
final int fianlVarI = i;
new Thread(() -> {
try {
Date date = tl.get().parse(dateArray[fianlVarI]);
String dateStr = tl.get().format(date);
System.out.println("【i:" + fianlVarI + "】:{ThreadId="
+ Thread.currentThread().getId() + "; Date=" + date
+ "; dateStr=" + dateStr + "}");
} catch (Exception e) {
System.out.println(Thread.currentThread().getId());
e.printStackTrace();
}
}).start();
}
}
}
【i:1】:{ThreadId=9; Date=Sun Feb 02 22:22:22 CST 1992; dateStr=1992-2-2 22:22:22}
【i:2】:{ThreadId=10; Date=Wed Mar 03 13:33:33 CST 1993; dateStr=1993-3-3 13:33:33}
【i:0】:{ThreadId=8; Date=Tue Jan 01 11:11:11 CST 1991; dateStr=1991-1-1 11:11:11}
再修改上方的示例代码,使initialValue()返回一个共享的变量(或set一个共享的变量),测试异常,说明ThreadLocal并不能解决共享变量的并发修改问题
package org.world.traveler.common;
import java.text.SimpleDateFormat;
import java.util.Date;
public final class ThreadLocalTest {
private ThreadLocalTest() {
assert false;
}
private final static SimpleDateFormat SDF = (SimpleDateFormat) SimpleDateFormat.getDateTimeInstance();
private final static ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
//initialValue()返回一个共享的对象。(或者用set设置一个共享的对象也是一样的效果)
return SDF;
}
};
public static void main(String[] args) throws ClassNotFoundException {
testUsStaticValue();
}
/**
* 测试set()使用共享的value
* 结论:同样会引发并发访问的问题
*/
public static void testUsStaticValue() {
final String[] dateArray = new String[] { "1991-01-01 11:11:11","1992-02-02 22:22:22", "1993-03-03 13:33:33" };
for (int i = 0; i < dateArray.length; i++) {
final int fianlVarI = i;
new Thread(() -> {
try {
Date date = tl.get().parse(dateArray[fianlVarI]);
String dateStr = tl.get().format(date);
System.out.println("【i:" + fianlVarI + "】:{ThreadId="
+ Thread.currentThread().getId() + "; Date=" + date
+ "; dateStr=" + dateStr + "}");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
java.lang.NumberFormatException: multiple points
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.lang.Double.parseDouble(Double.java:538)
at java.text.DigitList.getDouble(DigitList.java:169)
at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at org.world.traveler.common.ThreadLocalTest.lambda$0(ThreadLocalTest.java:38)
at java.lang.Thread.run(Thread.java:745)
......
【i:0】:{ThreadId=8; Date=Wed Jan 01 11:11:11 CST 1119; dateStr=1119-1-1 11:11:11}
最后再贴出ThreadLocal的相关源码以供参考
public ThreadLocal() {
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
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;
}
/**
* 创建或覆盖当前线程的threadLocalMap中tl对应的旧的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);
}
pulic void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
/**
* 返回当前线程的ThreadLocalMap对象
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* 赋值当前线程的ThreadLocalMap对象
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//...
}
覆盖当前线程的threadLocalMap中tl对应的旧的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);
}
pulic void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
/**
* 返回当前线程的ThreadLocalMap对象
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* 赋值当前线程的ThreadLocalMap对象
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//...
}