ThreadLocal使用的情景和原理
线程本地存储,ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。这是防止多线程在资源上产生冲突的第二种方式,即每个线程都有一个完整的副本,
该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
当多个线程需要使用同一个对象,并且需要该对象具有相同的初始化值 的情景下使用ThreadLocal
实例:
package concurrencypractice.jingtong.review;
public class MythreadExtendsThread extends Thread{
private sequenceB sequence;
public MythreadExtendsThread(sequenceB sequence){
this.sequence=sequence;
}
public void run(){
// TODO Auto-generated method stub
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName()+" i="+i+"sequence.getNumber()"+sequence.getNumber());
try {
Thread.sleep(i*1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
package concurrencypractice.jingtong.review;
public class sequenceB {
private static ThreadLocal<Integer> numberContainer=new ThreadLocal<Integer>()/*{
//threadLocal赋初值的方法一
protected Integer initialValue(){
return 0;
}
}*/;
public int getNumber(){
//threadLocal赋初值方法二
if(numberContainer.get()==null){
numberContainer.set(0);
}else{
numberContainer.set(numberContainer.get()+1);
}
return (int) numberContainer.get();
}
public static void main(String[] args) {
sequenceB sequnce=new sequenceB();
MythreadExtendsThread thread=new MythreadExtendsThread(sequnce);
MythreadExtendsThread thread1=new MythreadExtendsThread(sequnce);
MythreadExtendsThread thread2=new MythreadExtendsThread(sequnce);
thread.start();
thread1.start();
thread2.start();
}
}
打印结果
Thread-1 i=0sequence.getNumber()0
Thread-2 i=0sequence.getNumber()0
Thread-0 i=0sequence.getNumber()0
Thread-2 i=1sequence.getNumber()1
Thread-1 i=1sequence.getNumber()1
Thread-0 i=1sequence.getNumber()1
Thread-1 i=2sequence.getNumber()2
Thread-2 i=2sequence.getNumber()2
Thread-0 i=2sequence.getNumber()2
ThreadLocal的底层原理分析
本质上是每个线程内部有一个ThreadLocalMap,所以获取threadLocalmap需要先获取当前线程;该threadLocalMap中key为threadLocal自身,value为真实的值。
提供的方法: 先了解一下ThreadLocal类提供的几个方法:
1 2 3 4 |
public T get() { } public void set(T value) { } public void remove() { } protected T initialValue() { } |
get()方法是用来获取ThreadLocal在当前线程中保存的变量副本,set()用来设置当前线程中变量的副本,remove()用来移除当前线程中变量的副本,initialValue()是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法,下面会详细说明。
先看下get方法的实现:
第一句是取得当前线程,然后通过getMap(t)方法获取到一个map,map的类型为ThreadLocalMap。然后接着下面获取到<key,value>键值对,注意这里获取键值对传进去的是 this(就是ThreadLocal对象),而不是当前线程t。
如果获取成功,则返回value值。
如果map为空,则调用setInitialValue方法返回value。
我们上面的每一句来仔细分析:
首先看一下getMap方法中做了什么:
可能大家没有想到的是,在getMap中,是调用当期线程t,返回当前线程t中的一个成员变量threadLocals。
那么我们继续取Thread类中取看一下成员变量threadLocals是什么:
实际上就是一个ThreadLocalMap,这个类型是ThreadLocal类的一个内部类,我们继续取看ThreadLocalMap的实现:
可以看到ThreadLocalMap的Entry继承了WeakReference,并且使用ThreadLocal作为键值。
然后再继续看setInitialValue方法的具体实现:
很容易了解,就是如果map不为空,就设置键值对,为空,再创建Map,看一下createMap的实现:
至此,可能大部分朋友已经明白了ThreadLocal是如何为每个线程创建变量的副本的:
首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。
初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。
1)实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的;
2)为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,就像上面代码中的numberContainer
3)在进行get之前,必须先set,如果没有set则会调用setInitialValue初始化threadLocals,否则会返回null;
借鉴:
https://www.cnblogs.com/dolphin0520/p/3920407.html