用法
ThreadLocal jdk1.6 api的解释:
该类提供了线程局部 (thread-local)变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
是不是感觉很难懂....
先看代码
public class SequenceA {
private static int number = 0;
public synchronized int getNumber() {
number = number + 1;
return number;
}
public static void main(String[] args) {
final SequenceA sequence = new SequenceA();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " => " + sequence.getNumber());
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " => " + sequence.getNumber());
}
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " => " + sequence.getNumber());
}
}
});
thread1.start();
thread2.start();
thread3.start();
}
}
Thread-0 => 1
Thread-1 => 2
Thread-0 => 4
Thread-2 => 3
Thread-0 => 6
Thread-1 => 5
Thread-2 => 7
Thread-2 => 9
Thread-1 => 8
说明一下,为什么getNumber()方法前要加synchronized关键字,如果我们不加,就有可能线程执行的过快,导致线程之间可能拿到同一个数值增加。可能会得到下面的结果
Thread-0 => 2
Thread-2 => 2
Thread-1 => 1
Thread-2 => 4
Thread-0 => 3
Thread-2 => 6
Thread-1 => 5
Thread-0 => 7
Thread-1 => 8
main方法中模拟了三个线程,每一个都重写的run方法,目的就是为了验证一个问题,非线程安全。 每一个线程都调用了相同的number变量,而且线程之间的number值是共享的。
那么我们需要每一个线程都有自己的number值的单独增加,应该怎么做呢。
这就要引入ThreadLocal类,下面看看另一个实现。
public class SequenceB {
private static ThreadLocal threadLocal = new ThreadLocal() {
@Override
protected Integer initialValue() {
return 0;
}
};
public int getNumber() {
threadLocal.set(threadLocal.get() + 1);
return threadLocal.get();
}
public static void main(String[] args) {
final SequenceB sequenceb = new SequenceB();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " => " + sequenceb.getNumber());
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " => " + sequenceb.getNumber());
}
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " => " + sequenceb.getNumber());
}
}
});
thread1.start();
thread2.start();
thread3.start();
}
}
Thread-1 => 1
Thread-2 => 1
Thread-0 => 1
Thread-2 => 2
Thread-1 => 2
Thread-2 => 3
Thread-0 => 2
Thread-1 => 3
Thread-0 => 3
线程之间相互独立,没有被共享增加,为每一个线程提供了一个单独的副本。
下面看看api
protected T initialValue(){}
public T get(){}
public void set(T value){}
public void remove(){}
看api的字面意思应该很好理解,为什么我们使用之前要重写initialValue 方法呢??
接下来我们看源码
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;
}
protected T initialValue() {
return null;
}
如果我们在使用之前没有重写initialValue() 方法 而且先调用了get()方法的话,就会报NullPointerException异常。
实现思想
早期实现
ThreadLocal类创建一个Map,然后用线程的id作为Map的key,实例对象作为Map的value。这样做的确能达到效果,这是jdk1.3之前的实现方法。
后来发现效率很差,接着换成了 key为ThreadLocal实例本身,value是真正需要存储的Object。据说能提高性能... (当Thread销毁之后对应的ThreadLocalMap也就随之销毁了,能减少内存使用量。)
具体应用
ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。归纳了两点:
1。每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
2。将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。
ps:先占坑,等项目中用到,再更新运用场景。
参考:https://my.oschina.net/huangyong/blog/159725