ThreadLocal

以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(线程局部变量 或 线程本地变量)

  1. 每个Thread都维护着一个自己的ThreadLocakMap对象(弱引用), 这个map的Key就是一个个ThreadLocal实例。 通常甚至JDK文档中说的threadLocal的值并不是指threadLocal变量本身的地址值,而是指它对应线程中这个map中的值。 
  2. ThreadLocal变量的定义应该是private static final的,如果不是static那么使用ThreadLocal就失去了意义(和上面的第一种方式就没区别了),且这个变量本身不应该被某个线程所修改,所以一般加上final
  3. 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;
        }
    }
    
    //...
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值