使用线程局部变量
多线程应用中最关键的数据共享,当创建一个类实现了Runnable接口,该类中有一个私有属性;然后创建了多个都使用该类的线程,这些线程都共享这个属性,如果线程中有修改该属性的方法执行,那么该属性都将被这些线程修改,从而造成了错误;
有时候需要线程之间彼此互不相干,各自维护自己的局部变量;Java API提供了这样的机制,叫做 thread-local variable,即线程局部变量;
在下面的实例中展示这个机制,并简单的分析一下源码;
动手实现
(1)创建一个共享同一个属性的实例
public class UnsafeTask implements Runnable {
// Unsafe variable
private Date startDate;
@Override
public void run() {
startDate = new Date();
System.out.printf("Starting Thread_%s : %s\n", Thread.currentThread().getId(),
Utils.dateFormat(startDate));
try {
TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10 + 5));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("Thread finished_%s : %s\n", Thread.currentThread().getId(),
Utils.dateFormat(startDate));
}
public static void main(String[] args) {
UnsafeTask unsafeTask = new UnsafeTask();
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(unsafeTask);
thread.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
一次运行结果
Starting Thread_10 : 2014-11-01 20:40:24
Starting Thread_11 : 2014-11-01 20:40:25
Starting Thread_12 : 2014-11-01 20:40:26
Starting Thread_13 : 2014-11-01 20:40:27
Starting Thread_14 : 2014-11-01 20:40:28
Thread finished_11 : 2014-11-01 20:40:28
Thread finished_13 : 2014-11-01 20:40:28
Thread finished_12 : 2014-11-01 20:40:28
Thread finished_10 : 2014-11-01 20:40:28
Thread finished_14 : 2014-11-01 20:40:28
可以看到,线程启动的时间各部相同,因为创建每个线程之间睡眠了一秒钟;但是这些线程结束时间都是一样的;
(2)创建一个线程局部变量的实例
public class SafeTask implements Runnable {
private static ThreadLocal<Date> startDate=new ThreadLocal<Date>(){
@Override
protected Date initialValue() {
return new Date();
}
};
@Override
public void run() {
System.out.printf("Starting Thread_%s : %s\n", Thread.currentThread().getId(),
Utils.dateFormat(startDate.get()));
try {
TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10+5));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("Thread finished_%s : %s\n", Thread.currentThread().getId(),
Utils.dateFormat(startDate.get()));
}
public static void main(String[] args) {
SafeTask safeTask = new SafeTask();
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(safeTask);
thread.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
一次运行结果
Starting Thread_10 : 2014-11-01 20:42:53Starting Thread_11 : 2014-11-01 20:42:54
Starting Thread_12 : 2014-11-01 20:42:55
Starting Thread_13 : 2014-11-01 20:42:56
Starting Thread_14 : 2014-11-01 20:42:57
Thread finished_10 : 2014-11-01 20:42:53
Thread finished_11 : 2014-11-01 20:42:54
Thread finished_14 : 2014-11-01 20:42:57
Thread finished_12 : 2014-11-01 20:42:55
Thread finished_13 : 2014-11-01 20:42:56
要点
ThreadLocal通过initValue()方法创建并维护线程的这个属性值,可以通过get方法获取到;每一个启动的线程都将有一个ThreadLocal实例;这些实例都是通过new创建的,所以它们之间不会相干;
ThreadLocal提供了remove()方法,线程可以通过该方法删除其threadLocal中的变量;
简单源码分析
首先看下Thread类中的几个属性
public class Thread implements Runnable {
private Runnable target;
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
threadLocals 上面注释的是:ThreadLocal实现附属到这个线程,而维护这些threadlocal的map是由ThreadLocal维护,即ThreadLocal的内部静态类ThreadLocalMap;
在看ThreadLocal类的几个方法
public class ThreadLocal<T> {
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();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
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;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
}
可以看出,Thread类中的threadlocalMap是通过ThreadLocal中的set方法来引用和维护的;在一个线程启动后,当在线程内获取threadlocal局部变量时,threadlocal通过set方法将自身的引用设置到当前线程的ThreadLocalMap中,并通过initValue方法返回新创建的值,每个线程维护的threadlocal都不同;从而保证了变量的线程安全;