Java多线程----很多人问的ThreadLocal实现原理和过程(源码分析)

ThreadLocal 用法(如有错误,欢迎指正!)

与Synchonized 的比较

ThreadLocal 和 Synchonized 共同点在于都能用于解决多线程并发访问,可是 ThreadLocal 与 synchronized 有本质的差别:

  • synchronized 是利用锁的机制,使变量或代码块在某一时该仅仅能被一个线程访问。

  • ThreadLocal 为每个线程都提供了变量的副本,使得每个线程在某一时间訪问到的并非同一个对象,这样就隔离了多个线程对数据的数据共享,保证了线程间的安全

ThreadLocal在Spring事务管理过程中运用较为常见:

public void serviceMethod(){
	Connecion conn = null;
	try{
		Connection conn = getConnetion();
		conn.setAutoCommit(false);
		Dao1 dao1 = new Dao1(conn);
		dao1.doSomething();
		Dao2 dao2 = new Dao2(conn);
		dao2.doSomething();
		Dao3 dao3 = new Dao3(conn);
		dao3.doSomething();
		conn.commit();
	}
}
Class Dao1{
	private Connection conn = null;
	public Dao1(Connection conn){
		this.conn = conn;
	}
	public void doSomething(){
		PrepareStatement pstmt = null;
		try{
			pstmst = conn.prepareStatement(sql);
			pstmt.execute...	
		}
	}
}

上面的代码中未使用 ThreadLoca,Spring主要解决的是如何让Spring的多个执行阶段中链接同一个数据来源,若是不能保证数据源相同,代码都无法正确运行

要让三个Dao都连接相同数据源,可以给他们都传一个相同的数据库连接:

所以Spring事务通过多线程绑定解决这个问题,因为Dao的每一个请求周期是通过线程来执行,即运用ThreadLocal将数据库中的connection作为独立的副本绑定到每一个线程中,线程自然也可以独立改变自己的副本,也不影响其他线程所对应的副本,可以理解为各线程专有的本地变量
因为不管程序开启事务还是执行具体的 sql 都需要一个具体的数据库连接。

ThreadLocal 思路:

public class HYQ{
	public static ThreadLocal<Integer> tlForInteger = new ThreadLocal<Integer>(){
		@Override
		protected Integer initialValue(){
			return 1;
		}
	};
	public static ThreadLocal<String> tlForString = new TreadLocal<String>(){
		@Override
		protected String initialValue(){
			return "i miss u";
		}
	};
	public static class Tsthread implements Runnable{
		@Override
		public void run() {
			tlForString.set("thread");
			System.out.println(threadLocalLong.get());
			System.out.println(threadLocalString.get());
			}
	}
	public static void main(String[] args){
		tlForInteger.set(100L);
		tlForString.set("test");
		
		//打包 Runnable 和start()的方法,这里就不写了
		//new Thread(new Runnable() {}).start();

		Tsthread XYDD = new Tsthread();
		Thread thread = new Thread(XYDD);
		thread.start();
		
		System.out.println(threadLocalLong.get());
		System.out.println(threadLocalString.get());

	}
}

具体实现:

import java.util.HashMap;
import java.util.Map;
 
public class TaiYuan {
	public static void main(String [] args){
		ResourceHolder.putResource("conn",new Conn("connection1"));
		new Thread(new Runnable() {
			@Override
			public void run() {
				// 该线程不会得到主线程绑定的变量
				System.out.println(ResourceHolder.getResource("conn"));
			}
		}).start();
		
		System.out.println(ResourceHolder.getResource("conn"));
		new TaiYuan().function1();
		new Taiyuan().function2();
		System.out.println(ResourceHolder.getResource("conn"));
	}
	public void function1(){
		System.out.println(ResourceHolder.getResource("conn"));
	}
	public void function2(){
		System.out.println(ResourceHolder.getResource("conn"));
	}
}
 
class ResourceHolder{
	
	public static ThreadLocal<Map<Object,Object>> 
		threadLocalMap=new ThreadLocal<Map<Object,Object>>();
	public static void putResource(Object key,Object value){
		if(threadLocalMap.get()==null)
			threadLocalMap.set(new HashMap<Object,Object>());
		threadLocalMap.get().put(key, value);
	}
	public static Object getResource(Object key){
		if(threadLocalMap.get()==null)
			threadLocalMap.set(new HashMap<Object,Object>());
		return threadLocalMap.get().get(key);
	}
	public static void clearResource(Object key,Object value){
		if(threadLocalMap.get()!=null)
			threadLocalMap.remove();
	}
}
class Conn{
	private String name;
	
	public Conn(String name) {
		super();
		this.name = name;
	}
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
	@Override
	public String toString() {
		return "Conn [name=" + name + "]";
	}
}

ThreadLocal 实现

实现 ThreadLocal 类接口分别有四种方法:

  • void set(Object value)——设置当前线程的线程局部变量的值,意思就是你要吧那个变量当作副本。
  • public Object get()——该方法返回当前线程所对应的线程局部变量,用于变量发生改变。
  • public void remove()——将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是 JDK5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
  • protected Object initialValue()——返回该线程局部变量的初始值,该方法是一个 protected 的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第 1 次调用 get() 或 set(Object)时才执行,并且仅执行 1 次。ThreadLocal 中的缺省实现直接返回一个 null。

private static ThreadLocal <Integer/String> XYDD = new ThreadLocal <Integer/String> (){};
——XYDD 代表一个能够存放 Integer/String 类型的ThreadLocal 对象

上代码分析 ThreadLocal 的实现过程:

  1. 先定义一个静态 ThreadLocal 对象
//先初始化ThreadLocal,同时初始化Integer
private static THreadLocal<Integer> XYDD = new ThreadLocal<Integer>(){
	//重写初始化方法	
	@Override
	protected Integer initialValue(){
		//返回integer随便一个值
		Integer integer = 1;
		return integer;
	}
}

//ThreadLocal对象是可以定义多个的,并且是部疼痛类型的
//上面定义的integer类型的ThreadLocal,这里可以定义一个String的ThreadLocal
//且最终都会被保存在entry[]数组里面
private static ThreadLocal<String> stringThreadLocal;

  1. 创建一个方法用于开启多个线程用作测试
public void StartThreadArray(){
	//创建线程数组,用于下面遍历线程
	Thread[] xiaowang = new Thread[3];

	for(int i = 0; i < xiaowang.length; i++){
		//new一个自定义线程类的对象,传入自定义类里的局部变量的值
		TestThread WSQ = new TestThread(i+2);
		//创建线程,传入自定义线程类的对象
		Thread thread = new Thread(WSQ);
		//遍历把多线程赋值给线程数组
		xiaowang[i] = thread;
	}
	//通过遍历start每个线程
	for(int i = 0; i < xiaowang.length; i++){
		xiaowang[i].start();
	}
}
  1. 创建 Runnable 自定义线程类
/**
*创建测试线程,线程的工作是使 ThreadLocal 变量的的值发生变化,并重新写回
*测试线程之间是否会发生相互影响
*/
public static class TestThread implements Runnabble{
	int id = 0;
	public TestThread(int id){
		this.id = id;
	}
	public void run(){
		//搞清楚currentThread()的用处:
		//currentThread()源码解释:返回对当前正在执行的线程对象的引用
		//返回值:当前正在执行的线程
		//Thread.currentThread().getName():返回当前执行的线程的名字
		System.out.println(Thhread.currentThread().getName() + ":start");
		
		//get()方法:拿到threadLocal对象 XYDD的i值,i则为当前变量副本
		Integer q = XYDD.get();
		
		//更改 q 的值,要怎么把新的 q 设置为副本,
		//看下面的set()
		q = q + id;
		
		//set()的作用
		//@@@*****下面解释*****@@@@@
		XYYDD.set(q);

		System.out.println(Thread.currentThread().getName()+":"+XYDD.get());
	}
}

set() 的用处:

来看看源码的解释:将此线程局部变量的当前线程副本设置为指定值
放到这段代码块的意思就是:前面的get()方法已经拿到thresadLocal XYDD的初始化值,但是经过run()方法里的id,q的值变了,所以通过set()方法可以将新的 q 设置回去做新的副本
注意在这里怎么set()都是在该线程内部的entry[]搞定,不涉及其他线程和threadLocal

不管使set()还是get(),都是对自己独有的ThreadLocalMap进行操作的
也因为每个线程自己独有的ThreadLocalMap是别的线程访绝对问不到的
这就是为什么threadLocal能保证各线程之间的安全性

最后创建主线程

    public static void main(String[] args){
    	UseThreadLocal test = new UseThreadLocal();
        test.StartThreadArray();
    }

来看看运行结果:
在这里插入图片描述

思路都在注释里了,跟着走没问题的,下面注重对源码的分析


ThreadLocal 解析

直接来理解一下变量的副本到底是怎么创建的,最好的方法就是解析所执行的方法:

get()—返回当前线程所对应的线程局部变量

源码如下:

—get( )
    public T get() {
    
    	------1. 先拿到调用这个get()方法的当前线程
        Thread t = Thread.currentThread();

		------2. 再把当前线程 t 作为参数传给了 getmap()方法
		------3. 通过 getmap() 方法创建一个 map 对象!! 看下面的getmap() 解释
		------4. 通过 getmap() 拿到每个线程自己独有的 ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        
        ------判空
        if (map != null) {
        
        	------5. 拿enty条目,通过取threadLocal键对应的value
            ThreadLocalMap.Entry e = map.getEntry(this);

            if (e != null) {
            ------6.返回值
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

要搞明白就要先搞明白getMap()方法到底是什么:

—getMap()
    ThreadLocalMap getMap(Thread t) {
    -----调用 getMap()返回当前线程里的threadLocals
        return t.threadLocals;
    }

可以看到调用 getMap()返回线程里的 threadLocals ,再继续看这个 threadLocals 是什么:

—threadLocals
    /* ThreadLocal values pertaining to this thread. This map is maintainedby the ThreadLocal class. 
    与此线程相关的本地值。此映射由ThreadLocal类维护
	 */
    ThreadLocal.ThreadLocalMap threadLocals = null;
     

threadLocals 是一个定义在 ThreadLocal 里面的 ThreadLocalMap


所以每一个线程都有一个自己的成员变量,而这个成员类型是ThreadLocalMap,搞不懂ThreadLocalMap 是什么,没关系!继续:

—ThreadLocalMap
    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 t he code that follows.
         */
         ------定义一个Enty------
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
            
			//第一个参数是 key, 第二个参数是 value;
			//以threadLocal为键,以Object为Value
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        private static final int INITIAL_CAPACITY = 16;
        //Enty[] 被定义成了一个数组,因为传入的ThreadLocal可以是多个,一个线程拥有的ThreadLocal变量副本可能是多个
        private Entry[] table;
        private int size = 0;
        ...

Enty 为什么是个数组:

因为传入的ThreadLocal可以是多个,一个线程拥有的ThreadLocal变量副本可能是多个,数组初始化大小是16

ThreadLocalMap 表示每个线程自己会有一个 ThreadLocalMap,本质上还是一个Map, 只不过每个这个 Map 中间对每个条目来说是以 ThreadLocal 为键盘,以传入的 Object 的值为Value


set()—设置当前线程的线程局部变量的值

—set()
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

其实 set() 也是差不多的, currentThread() 拿到当前线程,getMap()threadLocalsthreadLocals 又是个 threadLocalMap,也就是拿 Enty 条目
有点区别的就是 createMap(),看下面吧


—getMap()

    /**
     * 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;
    }

return threadLocals,得到的threadLocalsset() 相同


—createMap()

    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

拿到当前线程的 threadLocals,是通过 new 一个 ThreadLocalMap 类的实例实现,传入第一个参数是 this 代表当前线程,第二个参数是 firstValue


—ThreadLocalMap()

这个方法啊,也就是ThreadLocalMap 类中的方法,该咋实现咋实现


        /**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
         -------还是 key Value 类型
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        
        	-------Enty[] 实例,大小16
            table = new Entry[INITIAL_CAPACITY];
			
			-------下面是判断ThreadLocal的副本有没重复,没有重复就返回
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

ThreadLocalMapThreadLocal 的静态内部类,每一个 Thread,都有一个自己的 ThreadLocalMap
ThreadLocalMap 里是一个 Entry 数组。


—threadLocalHashCode & nextHashCode

目的是 set() 中采用 nexHash,重复计算哈希值来判断ThreadLocal是否重复

private final int threadLocalHashCode = nextHashCode();
private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

—Entry类

        /**
         * 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;
            }
        }

EntryThreadLocalMap 的静态内部类,ThreadLocal 与副本对象,做成了 key-value 的形式。
它继承了 WeakReference,总之它记录了两个信息,一个是 ThreadLocal<?> 类型,一个是 Object 类型的值。反正 getEntry() 则是获取某个 ThreadLocal 对应的值, set() 就是更新或赋值相应的 ThreadLocal 对应的值。


总结:
在这里插入图片描述

  1. 先定义一个某个类型的ThreadLocal
  2. 比如有两个线程,一个小扬,一个小钊,当小扬过来的时候,就现在自己的内部声明一个 ThreadLocalMap,而 ThreadLocalMap 里面是有 Enty[ ] 的,数组里面又有多个条目,且每个条目以 key-value 形式存在
  3. 然后以 ThreadLocal 为键保存一个当前线程自己的value
  4. 当第二个线程来了后也一样的顺序
  5. 当定义了第二个类型的 ThreadLocal 也是一样可以保存在条目中

在这里插入图片描述
                                    ——谢谢你阿蒨

  • 7
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值