Java多线程-理解ThreadLocal

Java多线程-理解ThreadLocal

此文章是参考许多博客写出来的,如果有不清楚的请往这些博客详细查看

ThreadLocal作用

ThreadLocal不是用来解决共享对象的多线程访问问题的,通过ThreadLocal的set()方法设置到线程的ThreadLocal.ThreadLocalMap里的是是线程自己要存储的对象,其他线程不需要去访问,也是访问不到的。各个线程中的ThreadLocal.ThreadLocalMap以及ThreadLocal.ThreadLocal中的值都是不同的对象

ThreadLocal初始化

当我们执行new ThreadLocal时执行的步骤
1、步骤一

private final int threadLocalHashCode = nextHashCode();//堆当中

2、步骤二

private static int nextHashCode(){

        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

3、在上面两个步骤执行的时候,如果我们的类是第一创建,那么我们首先会执行下面的两个操作,并将类变量和常量放到方法区当中

private static AtomicInteger nextHashCode =new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;

4、

/**
     * 为什么每一次new 一个ThreadLocal的threadLocalHashCode 值都会增加HASH_INCREMENT
     * 这是因为当我们在初始化一个ThreadLocal的时候我们必须首先初始化我们的final类型的变量threadLocalHashCode
     * 然后会调用nextHashCode(),我们的nextHashCode变量是一个static类型的变量,
     * 也就是说我们每一次都会对nextHashCode=nextHashCode+HASH_INCREMENT,然后就会nextHashCode这个变量是所有类都共享的,保存在方法区当中
     */

//实例变量
private final int threadLocalHashCode = nextHashCode();

//类变量
private static AtomicInteger nextHashCode = new AtomicInteger();//执行步骤1

//常量
private static final int HASH_INCREMENT = 0x61c88647;//执行步骤2

//静态方法
private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
}
//构造方法
public ThreadLocal() {
}

自己写的一个测试

import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by luckyboy on 2018/7/6.
 */
public class ThreadLocalTest_I {
    private final int threadLocalHashCode = nextHashCode();//堆当中
    private final static int HASH_INCREMENT =  0x61c88647;//内存位置:方法区

    private static AtomicInteger nextHashCode = new AtomicInteger();//内存位置:方法区

    private static int nextHashCode(){

        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

    public int getThreadLocalHashCode(){
        return this.threadLocalHashCode;
    }
    public static void main(String[] args){
        ThreadLocalTest_I  threadLocal = new ThreadLocalTest_I();
        ThreadLocalTest_I  threadLocal1 = new ThreadLocalTest_I();
        System.out.println(threadLocal.getThreadLocalHashCode());
        System.out.println(threadLocal1.getThreadLocalHashCode());
    }
}

输出结果

0
1640531527

ThreadLocal.get

get()
getMap(Thread t)
setInitialValue()
initialValue()
createMap(Thread t, T firstValue) {

1、获取当前线程并获取线程的ThreadLocalMap类型的map对象
2、判断map、Entry是否为空,如果二者不为空,那么我们就根据ThreadLocal对象在map中找到对应的result返回;二者如果有一个为空那么转到步骤3
3、setInitialValue()方法会再一次判断map是否为空,如果不为空则表示我们的Entry为空,添加键值对(Key 是我们传递进来的ThreadLocal对象-隐式参数this,value是传递进来的显示参数),如果map为空,那么我们转到步骤4
4、createMap(Thread t, T firstValue)为当前的线程创建一个ThreadLocalMap并将ThreadLocal-firstValue添加到ThreadLocalMap中。返回一个ThreadLocalMap,然后setInitialValue就会返回一个value=null的值

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {                //map != null && e != null返回一个value
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();         //map == null || e == null返回一个null
}

在上面的map为空和Entry为空都有会调用该setInitialValue()
private T setInitialValue() {
    T value = initialValue();//调用initialValue()返回一个null值
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)         //如果map存在则之间添加ThreadLocal-null
        map.set(this, value);
    else                     // 如果不存在则为当前的Thread创建一个ThreadLocalMap对象
        createMap(t, value);
    return value;            //null
}

//
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocal.set

1、执行getMap()获取currentThread的ThreadLocalMap对象map
2、判断map是否为空,map如果不为空,则添加隐式参数this代表的当前的ThreadLocal对象和对应的value值到map中,如果为空则到步骤3
3、为当前线程创建一个ThreadLocalMap对象,然后添加ThreadLocal-value;

//getMap获取当前线程的ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}


//set方法为当前Thread添加一个ThreadLocal-value到ThreadLocalMap当中
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}


//currentThread的ThreadLocalMap为空则创建一个map,添加ThreadLocal-value
void createMap(Thread t, T firstValue) {
   t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocal.ThreadLocalMap

ThreadLocalMap是ThreadLocal中的一个静态内部类,当我们要往一个线程中添加一个ThreadLocal时,我们首先会获取当前线程的ThreadLocalMap,然后给将ThreadLocal作为一个键添加到线程的ThreadLocalMap中。
ThreadLocal采用的是线性探查法,也就是开地址寻址法的方式去根据ThreadLocal是否存在ThreadLocalMap中
首先我们来看一下ThreadLocalMap中的数据结构和重要的参数

//map中的元素Entry就是我们常说的key-value形式
static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

//map采用数组的方式存储我们的Entry,这里并不是采用拉链法解决hash冲突问题
private Entry[] table;

//map中初始数组的大小,必须是2的次幂
private static final int INITIAL_CAPACITY = 16;

ThreadLocal的构造方法

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];//创建一个大小为16的Entry数组
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);//
    table[i] = new Entry(firstKey, firstValue);   //添加key-value
    size = 1;                    //初始化map中Entry的数量
    setThreshold(INITIAL_CAPACITY);//设置rehash的阈值为16
}

其中最重要的一条是我们如果将我们的ThreadLocal放到Map中的Entry[]那个位置

/**
*不管firstKeyfirstKey.threadLocalHashCode的值有多大,
*和INITIAL_CAPACITY-1进行&操作后,范围都是在0~INITIAL_CAPACITY-1
*/
firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);

我们看一下getEntry()

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);  //根据ThreadLocal.threadLocalHashCode确定ThreadLocal在Entry中的位置
    Entry e = table[i];
    if (e != null && e.get() == key) // 如果Entry存在且Entry的Key和ThreadLocal.threadLocalHashCode相等,表明找到了ThreadLocal对应的Entry,此时返回Entry
        return e;
    else
        return getEntryAfterMiss(key, i, e);//如果Entry == null 或者 Entry对应的key和ThreadLocal.threadLocalHashCode不相等,则调用此方法法
}

//getEntryAfterMiss(key, i, e)
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;

    while (e != null) {
        ThreadLocal<?> k = e.get();
        if (k == key)//如果对应的Entry不为空,且this.ThreadLocal == e.get(),那么此时返回Entry
            return e;
        if (k == null)//如果对应的Entry的key为null,那么此时调
            expungeStaleEntry(i);
        else//如果上面两个步骤都执行完了,表明this.ThreadLocal.threadLocalHashCode确实存在与表中,但是是其他ThreadLocal具有相同的ThreadLocalHashCode的位置
            i = nextIndex(i, len);那么这个时候就需要进行下一个位置查找了
        e = tab[i];
    }
    return null;
}

看一下

private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;

    // expunge entry at staleSlot
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;

    // Rehash until we encounter null
    Entry e;
    int i;
    for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();
        if (k == null) {
        e.value = null;
        tab[i] = null;
            size--;
        } else {
            int h = k.threadLocalHashCode & (len - 1);
            if (h != i) {
               tab[i] = null;

               // Unlike Knuth 6.4 Algorithm R, we must scan until
               // null because multiple entries could have been stale.
               while (tab[h] != null)
                   h = nextIndex(h, len);
                   tab[h] = e;
               }
         }
    }
    return i;
}

ThreadLocal实例

import java.util.concurrent.TimeUnit;

/**
 * Created by luckyboy on 2018/7/6.
 */
public class ThreadLocalTest_II {
    public static void main(String[] args){
        UnsafeTask task = new UnsafeTask();
        //SafeTask task = new SafeTask();
        for(int i = 0;i<10 ;i++){
            Thread thread = new Thread(task);
            thread.start();
            try {
                TimeUnit.SECONDS.sleep(2);
                //Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class UnsafeTask implements Runnable{
    private Date startDate;
    @Override
    public void run() {
        startDate = new Date();
        System.out.printf("Starting Thread:%s : %s\n",Thread.currentThread().getId(),startDate);
        try {
            TimeUnit.SECONDS.sleep((long)Math.rint(Math.random()*10));
            System.out.printf("Thread Finished:%s : %s\n",Thread.currentThread().getId(),startDate);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

输出

Starting Thread:11 : Fri Jul 06 20:26:53 CST 2018
Starting Thread:12 : Fri Jul 06 20:26:55 CST 2018
Starting Thread:13 : Fri Jul 06 20:26:57 CST 2018
Thread Finished:12 : Fri Jul 06 20:26:57 CST 2018
Starting Thread:14 : Fri Jul 06 20:26:59 CST 2018
Thread Finished:13 : Fri Jul 06 20:26:59 CST 2018
Starting Thread:15 : Fri Jul 06 20:27:01 CST 2018
Thread Finished:11 : Fri Jul 06 20:27:01 CST 2018
Starting Thread:16 : Fri Jul 06 20:27:03 CST 2018
Thread Finished:14 : Fri Jul 06 20:27:03 CST 2018
Starting Thread:17 : Fri Jul 06 20:27:05 CST 2018
Starting Thread:18 : Fri Jul 06 20:27:07 CST 2018
Thread Finished:17 : Fri Jul 06 20:27:07 CST 2018
Thread Finished:15 : Fri Jul 06 20:27:07 CST 2018
Starting Thread:19 : Fri Jul 06 20:27:09 CST 2018
Thread Finished:18 : Fri Jul 06 20:27:09 CST 2018
Starting Thread:20 : Fri Jul 06 20:27:11 CST 2018
Thread Finished:16 : Fri Jul 06 20:27:11 CST 2018
Thread Finished:19 : Fri Jul 06 20:27:11 CST 2018
Thread Finished:20 : Fri Jul 06 20:27:11 CST 2018

再看看我们的Date对象放在ThreadLocal当中

public class ThreadLocalTest_II {
    public static void main(String[] args){
        UnsafeTask task = new UnsafeTask();
        //SafeTask task = new SafeTask();
        for(int i = 0;i<10 ;i++){
            Thread thread = new Thread(task);
            thread.start();
            try {
                TimeUnit.SECONDS.sleep(2);
                //Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class SafeTask implements Runnable{
    /***
     * 这一刻发生了什么,当我们创建一个线程的时候我们首先会先创建一个ThreadLocal类,
     * ThreadLocal 重写了initialValue(),我们当我们调用get()方法的时候会用到initialValue(),然后返回initialValue方法的值
     */
    private static ThreadLocal<Date> startDate = new ThreadLocal<Date>(){
        protected  Date initialValue(){
            return new Date();
        }
    };
    @Override
    public void run() {
        System.out.printf("Starting Thread:%s : %s\n",Thread.currentThread().getId(),startDate.get());
        try {
            TimeUnit.SECONDS.sleep((long)Math.rint(Math.random()*10));
            System.out.printf("Thread Finished:%s : %s\n",Thread.currentThread().getId(),startDate.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出结果

Starting Thread:11 : Fri Jul 06 20:32:05 CST 2018
Starting Thread:12 : Fri Jul 06 20:32:07 CST 2018
Starting Thread:13 : Fri Jul 06 20:32:09 CST 2018
Thread Finished:11 : Fri Jul 06 20:32:05 CST 2018
Starting Thread:14 : Fri Jul 06 20:32:11 CST 2018
Thread Finished:12 : Fri Jul 06 20:32:07 CST 2018
Starting Thread:15 : Fri Jul 06 20:32:13 CST 2018
Starting Thread:16 : Fri Jul 06 20:32:15 CST 2018
Thread Finished:13 : Fri Jul 06 20:32:09 CST 2018
Starting Thread:17 : Fri Jul 06 20:32:17 CST 2018
Thread Finished:14 : Fri Jul 06 20:32:11 CST 2018
Thread Finished:17 : Fri Jul 06 20:32:17 CST 2018
Starting Thread:18 : Fri Jul 06 20:32:19 CST 2018
Starting Thread:19 : Fri Jul 06 20:32:21 CST 2018
Thread Finished:15 : Fri Jul 06 20:32:13 CST 2018
Thread Finished:18 : Fri Jul 06 20:32:19 CST 2018
Starting Thread:20 : Fri Jul 06 20:32:23 CST 2018
Thread Finished:19 : Fri Jul 06 20:32:21 CST 2018
Thread Finished:16 : Fri Jul 06 20:32:15 CST 2018
Thread Finished:20 : Fri Jul 06 20:32:23 CST 2018

结果分析:

参考文献

http://www.cnblogs.com/xrq730/p/4854820.html
https://www.cnblogs.com/dolphin0520/p/3920407.html
http://www.iteye.com/topic/103804
http://ifeve.com/thread-management-10/

猜你喜欢

转载自blog.csdn.net/makeliwei1/article/details/80935308