参考资料:http://www.cnblogs.com/dolphin0520/p/3920407.html
先来看看为什么用ThreadLocal,上一篇博客说的很好了,就好比,你要让你的线程链接数据库,如果你让这些线程共享一个数据库链接的话,就会出问题:
代码来自参考博客:
class ConnectionManager {
private static Connection connect = null;
public static Connection openConnection() {
if(connect == null){
connect = DriverManager.getConnection();
}
return connect;
}
public static void closeConnection() {
if(connect!=null)
connect.close();
}
}
当一个线程正在使用的时候,其他的线程可能会关闭这个链接,肯定就出问题了,要解决这个问题,你除了在每个线程单独的链接之外还可以使用ThreadLocal来解决这个问题。
我们把问题简化:我让三个线程随机输出5到1
先看代码:
class MyThread extends Thread {
private int i = 5;
@Override
public void run() {
for(int j = 0; j<20; j++) {
synchronized (this) {
if(i>0) {
System.out.println(
Thread.currentThread().getName() + ";" + i--);
}
}
}
}
}
public class TestMain {
public static void main(String ar[]) {
MyThread m1 = new MyThread();
Thread mt1 = new Thread(m1);
Thread mt2 = new Thread(m1);
Thread mt3 = new Thread(m1);
mt1.start();
mt2.start();
mt3.start();
}
}
运行结果:
现在,我想让每个 线程都重5到1输出一遍,这个问题可以有其他的结局问题,这里讨论的是ThreadLocal,就用ThreadLocal解决
代码:
class MyThread extends Thread {
private ThreadLocal<Integer> i = new ThreadLocal<Integer>();
public void set() {
i.set(5);
}
public void run() {
set();
int si = i.get();
for(int j = 0; j<20; j++) {
si = i.get();
if(si>0) {
System.out.println(Thread.currentThread().getName() + ";" + si-- );
i.set(si);
}
}
}
}
public class TestMain {
public static void main(String ar[]) {
MyThread m1 = new MyThread();
Thread mt1 = new Thread(m1);
Thread mt2 = new Thread(m1);
Thread mt3 = new Thread(m1);
mt1.start();
mt2.start();
mt3.start();
}
}
结果:
这样就相当于每个线程内部都有一个 i 这个i 只有当前线程自己能访问,很多地方把ThreadLocal叫做线程本地变量的原因。
现在来看看ThreadLocal怎么保存变量副本的:
public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }
这是ThreadLocal中经常用到的几个方法。
先看看get()
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();
//得到当前线程
ThreadLocalMap map = getMap(t);
//通过getMap(t)方法得到一个ThreadLocalMap
if (map != null) {
//如果map不为空,这将ThreadLocal作为键值,从map中取出一个Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
//如果这个Entry不为空,就将他的值返回
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//如果当前map为null,或者entry为空,调用setInitialValue()方法
return setInitialValue();
}
/*
* 再来看看setInitialValue()
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
private T setInitialValue() {
/*这个可以理解为得到一个值*/
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
/*取出当前线程的map,如果为null则创建一个*/
if (map != null)
map.set(this, value);
else
createMap(t, value);
/*这个方法最终会返回一个值,不管是创建一个map还是往里面放一个值*/
return value;
}
/*最后来看看ThreadLocalMap ,这个map到底是什么*/
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
/*threadLocals定义在,Thread.java中,这是Thread.java中的源码*/
ThreadLocal.ThreadLocalMap threadLocals = null;
/**
* ThreadLocalMap is a customized hash map suitable only for
* maintaining thread local values. No operations are exported
* outside of the ThreadLocal class. The class is package private to
* allow declaration of fields in class Thread. To help deal with
* very large and long-lived usages, the hash table entries use
* WeakReferences for keys. However, since reference queues are not
* used, stale entries are guaranteed to be removed only when
* the table starts running out of space.
*/
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/*由此可见ThreadLocalMap是ThreadLocal中的一个内部类*/
现在理解ThreadLocal也就简单一点了:
首先,每个Thread中有一个ThreadLocal的内部类ThreadLocalMap的成员变量threadLocals ,这个threadLocals 就是来保存当前Thread的实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。
再来说get(),get()会返回当前ThreadLocal为键的threadLocals中的值,
如果set()之前 get()这时候 会报空指针异常,(原因稍后再说)
然后看看set(T value)的代码:
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
这时候看就容易了,
然后就是总结了:
1)实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的;
2)每个线程中可有多个threadLocal变量,记录不同的副本,类似id,name
3)在进行get之前,必须先set,否则会报空指针异常;
然后就是怎么处理这个空指针异常:
private T setInitialValue() {
/*这个可以理解为得到一个值*/
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
/*取出当前线程的map,如果为null则创建一个*/
if (map != null)
map.set(this, value);
else
createMap(t, value);
/*这个方法最终会返回一个值,不管是创建一个map还是往里面放一个值*/
return value;
}
如果你没有set()就调用get(),T value = initialValue();这个方法返回的应该是null,这就是原因。
所有如果你不想空指针异常就要重写这个方法:
例如:
class MyThread extends Thread {
private ThreadLocal<Integer> i = new ThreadLocal<Integer>();
public void set() {
i.set(5);
}
public void run() {
/*如果将这个 set()去掉,程序会空指针异常*/
// set();
int si = i.get();
for(int j = 0; j<20; j++) {
si = i.get();
if(si>0) {
System.out.println(Thread.currentThread().getName() + ";" + si-- );
i.set(si);
}
}
}
}
public class TestMain {
public static void main(String ar[]) {
MyThread m1 = new MyThread();
Thread mt1 = new Thread(m1);
Thread mt2 = new Thread(m1);
Thread mt3 = new Thread(m1);
mt1.start();
mt2.start();
mt3.start();
}
}
这时如果自己重写initialValue()方法就可以避免:
class MyThread extends Thread {
private ThreadLocal<Integer> i = new ThreadLocal<Integer>(){
protected Integer initialValue() {
return 5;
};
};
public void set() {
i.set(5);
}
public void run() {
/*这时就去掉set()不会报错*/
//set()
int si = i.get();
for(int j = 0; j<20; j++) {
si = i.get();
if(si>0) {
System.out.println(Thread.currentThread().getName() + ";" + si-- );
i.set(si);
}
}
}
}
public class TestMain {
public static void main(String ar[]) {
MyThread m1 = new MyThread();
Thread mt1 = new Thread(m1);
Thread mt2 = new Thread(m1);
Thread mt3 = new Thread(m1);
mt1.start();
mt2.start();
mt3.start();
}
}
结果:
今晚就写到这里了,如果有错误,欢迎讨论!以后要是深入研究会更新的