ThreadLocal和ThreadLocalMap原理

转载https://www.jianshu.com/p/ee8c9dccc953

1线程局部变量ThreadLocal

  • 避免共享变量,保证各自线程的变量是独立的
  • 为各个线程提供各自的实例
  • 在一个线程中首次调用get时,会调用initalValue方法,在此之后,get方法会返回属于当前线程的那个实例
  • ThreadLocal 不是用于解决共享变量的问题的,也不是为了协调线程同步而存在,而是为了方便每个线程处理自己的状态而引入的一个机制。
  • 每个Thread内部都有一个ThreadLocal.ThreadLocalMap类型的成员变量,该成员变量用来存储实际的ThreadLocal变量副本。
  • ThreadLocal并不是为线程保存对象的副本,它仅仅只起到一个索引的作用。它主要是为每一个线程隔离一个类的实例,这个实例的作用范围仅限于线程内部。
 T get()  返回此线程局部变量的当前线程副本中的值。

 protected  T  initialValue() 
 返回此线程局部变量的当前线程的“初始值”,该方法不能显示调用,只有在第一次调用get()或者set()方法时才会被执行,并且仅执行1次。
 void	remove()  移除此线程局部变量当前线程的值。

 void	set(T value)  将此线程局部变量的当前线程副本中的值设置为指定值。

1.1ThreadLocal使用案例

案例

public class MyThread implements Runnable {
	//创建任意锁对象
	private Object obj = new Object();
	
	private static final ThreadLocal<SimpleDateFormat> dateFormat = new ThreadLocal<SimpleDateFormat>() {
	@Override
	protected SimpleDateFormat initialValue() {
		return new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");
	}
};
	@Override
	public void run() {
		synchronized (obj){//
				try {
					System.out.println(Thread.currentThread().getName() + ":" + dateFormat.get().format(new Date()));
					Thread.sleep(2000); //3个线程同时进来,都停在这里,线程1执行完,这时max=2
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
	}
	
	public static void main(String[] args) {
		MyThread st = new MyThread();
		// 创建三个线程对象
		Thread t1 = new Thread(st, "线程1");
		Thread t2 = new Thread(st, "线程2");
		Thread t3 = new Thread(st, "线程3");
		t1.start();
		t2.start();
		t3.start();
	}
}
结果:
线程2:2018-10-27 18-06-58
线程1:2018-10-27 18-07-00
线程3:2018-10-27 18-07-02

案例2
public class MyThread2 implements Runnable {
	//创建任意锁对象
	private Object obj = new Object();
	private static final ThreadLocal<Integer> score = new ThreadLocal<Integer>() {
	@Override
	protected Integer initialValue() {
		return 0;
	}
};
	@Override
	public void run() {
		
		String threadName=Thread.currentThread().getName();
		if(threadName.equals("线程1")){
			score.set(99);
		}else  if(threadName.equals("线程2")){
			score.set(98);
		}else  if(threadName.equals("线程3")){
			score.set(97);
		}
		synchronized (obj){//
		try {
			 System.out.println(Thread.currentThread().getName() + ":" + score.get());
			 Thread.sleep(1000); //3个线程同时进来,都停在这里,线程1执行完,这时max=2
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
	}
	
	public static void main(String[] args) {
		MyThread2 st = new MyThread2();
		// 创建三个线程对象
		Thread t1 = new Thread(st, "线程1");
		Thread t2 = new Thread(st, "线程2");
		Thread t3 = new Thread(st, "线程3");
		t1.start();
		t2.start();
		t3.start();
	}
}
结果:
线程1:99
线程2:98
线程3:97

2原理

ThreadLocal源码
set操作


    public void set(T value) {
    //用当前线程对象的引用作为key来设置map
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

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

你会看到,set需要首先获得当前线程对象Thread的引用;
然后取出当前线程对象的成员变量ThreadLocalMap;
如果ThreadLocalMap存在,那么进行key/value设置,key就是当前线程对象的引用;
如果ThreadLocalMap没有,那么创建一个;

get操作

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
        //this是ThreadLocal类的引用
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

通过当前线程对应的引用(key),获取ThreadLocalMap ,通过ThreadLocal类的引用,获取对应的Entry,
Entry的KEY就是当前线程的引用,VALUE就是值。
如果Entry不存在,就获取初始化值

initialValue()

protected T initialValue() {
        return null;
    }
该方法定义为protected级别且返回为null,很明显是要子类实现它的,所以我们在使用ThreadLocal的时候一般都应该覆盖该方法。
该方法不能显示调用,只有在第一次调用get()或者set()方法时才会被执行,并且仅执行1次。

3Thread、ThreadLocal、ThreadLocalMap的关系

在这里插入图片描述
3.1ThreadLocalMap
1ThreadLocalMap其内部利用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;
            }
        }

Entry的key就是ThreadLocal,而value就是值。同时,Entry也继承WeakReference,所以说Entry所对应key(ThreadLocal实例)的引用为一个弱引用
(关于弱引用博客:Java 理论与实践: 用弱引用堵住内存泄漏https://www.ibm.com/developerworks/cn/java/j-jtp11225/)

2ThreadLocalMap两个最核心的方法getEntry()、set(ThreadLocal> key, Object value)方法。
set(ThreadLocal> key, Object value)方法

 
    private void set(ThreadLocal<?> key, Object value) {
 
        ThreadLocal.ThreadLocalMap.Entry[] tab = table;
        int len = tab.length;
 
        // 根据 ThreadLocal 的散列值,查找对应元素在数组中的位置
        int i = key.threadLocalHashCode & (len-1);
 
        // 采用“线性探测法”,寻找合适位置
        for (ThreadLocal.ThreadLocalMap.Entry e = tab[i];
            e != null;
            e = tab[i = nextIndex(i, len)]) {
 
            ThreadLocal<?> k = e.get();
 
            // key 存在,直接覆盖
            if (k == key) {
                e.value = value;
                return;
            }
 
            // key == null,但是存在值(因为此处的e != null),说明之前的ThreadLocal对象已经被回收了
            if (k == null) {
                // 用新元素替换陈旧的元素
                replaceStaleEntry(key, value, i);
                return;
            }
        }
 
        // ThreadLocal对应的key实例不存在也没有陈旧元素,new 一个
        tab[i] = new ThreadLocal.ThreadLocalMap.Entry(key, value);
 
        int sz = ++size;
 
        // cleanSomeSlots 清除陈旧的Entry(key == null)
        // 如果没有清理陈旧的 Entry 并且数组中的元素大于了阈值,则进行 rehash
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();
    }

set()操作除了存储元素外,还有一个很重要的作用,就是replaceStaleEntry()和cleanSomeSlots(),这两个方法可以清除掉key == null 的实例,防止内存泄漏。在set()方法中还有一个变量很重要:threadLocalHashCode,定义如下:

private final int threadLocalHashCode = nextHashCode();

threadLocalHashCode应该是ThreadLocal的散列值,定义为final,表示ThreadLocal一旦创建其散列值就已经确定了,生成过程则是调用nextHashCode():

 //nextHashCode :表示分配下一个ThreadLocal实例的threadLocalHashCode的值
    private static AtomicInteger nextHashCode = new AtomicInteger();
 //HASH_INCREMENT :表示分配两个ThradLocal实例的threadLocalHashCode的增量
    private static final int HASH_INCREMENT = 0x61c88647;
 
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
    nextHashCode:
    

getEntry()方法

  private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

由于采用了开放定址法,所以当前key的散列值和元素在数组的索引并不是完全对应的,首先取一个探测数(key的散列值),如果所对应的key就是我们所要找的元素,则返回,否则调用getEntryAfterMiss(),如下:

 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)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);//该方法用于处理key == null,有利于GC回收,能够有效地避免内存泄漏。
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

猜你喜欢

转载自blog.csdn.net/zhou920786312/article/details/83572919
今日推荐