Java并发编程:深入剖析ThreadLocal

一.什么是ThreadLocal

二.深入解析ThreadLocal

三.巧用ThreadLocal

 

  1. ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

     
  2. 深入源码一探究竟

    a.首先了解一下几个通用的方法

    public T get() { }

    public void set(T value) { }

    public void remove() { }

    protected T initialValue() { }   


    get()方法是用来获取ThreadLocal在当前线程中保存的变量副本,set()用来设置当前线程中变量的副本,remove()用来移除当前线程中变量的副本,initialValue()是一个protected方法,一般是用来在使用时进行重写的,在不调用set()方法的情况下,靠此方法初始化副本的变量

    b.首先我们先来看一下get方法的实现

        /**
         * Returns the value in the current thread's copy of this
         * thread-local variable.  If the variable has no value for the
         * current thread, it is first initialized to the value returned
         * by an invocation of the {@link #initialValue} method.
         *
         * @return the current thread's value of this thread-local
         */
        public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }

           第一句是取得当前线程,然后通过getMap(t)方法获取到一个map,map的类型为ThreadLocalMap。然后接着下面获取到<key,value>键值对,注意这里获取键值对传进去的是  this,而不是当前线程t。如果map不为空,则返回value值。否则则调用setInitialValue方法返回value。
     
    b.再看一下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;
        }

           在getMap中,是调用当期线程t,返回当前线程t中的一个成员变量threadLocals。

    c.那么我们继续取Thread类中取看一下成员变量threadLocals是什么:

        /* ThreadLocal values pertaining to this thread. This map is maintained
         * by the ThreadLocal class. */
        ThreadLocal.ThreadLocalMap threadLocals = null;

    实际上就是一个ThreadLocalMap,这个类型是ThreadLocal类的一个内部类.

    d.我们继续取看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 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;
                }
            }
    
            //此处省略部分。。。。。。。。。。
    }

    可以看到ThreadLocalMap的Entry继承了WeakReference,并且使用ThreadLocal作为键值。


    e.然后再继续看setInitialValue方法的具体实现:

        /**
         * Variant of set() to establish initialValue. Used instead
         * of set() in case user has overridden the set() method.
         *
         * @return the initial value
         */
        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;
        }

    T value = initialValue();一般我们会重写此方法,走到这里会调用我们自己写的initialValue();方法,该方法用于给线程副本初始化,在接着代码就是,如果map不为空,就设置键值对,如果为空,再创建Map.

    f.看一下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);
        }

      createMap()就是为接上面map为空的情况下,来为ThreadLocalMap初始化的


          g.至此,可能大部分朋友已经明白了ThreadLocal是如何为每个线程创建变量的副本的,再总结一下,首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。

  3. 巧用threadLocals
           利用threadLocals我们可以将数据库连接对象,或者一些上下文对象,保存在当前每个线程副本中。这样我们就不用在将这个变量用于参数传递,并且在每个线程中都可以获取,并且互不影响的,大大简化了操作,下面就医数据库连接对象为例,贴出代码实现。

     

    import java.sql.Connection;
    import java.sql.SQLException;
    
    import javax.sql.DataSource;
    
    import com.mchange.v2.c3p0.ComboPooledDataSource;
    
    /**
     * 等效 JdbcUtils,用于提供获得连接工具类
     */
    public class C3P0Utils {
    	
    	//连接池
    	private static ComboPooledDataSource dataSource = new ComboPooledDataSource("test"); 
    	
    	//提供ThreadLocal用于为不同线程缓存连接
    	private static ThreadLocal<Connection> local = new ThreadLocal<Connection>();
    	
    	/**
    	 * 获得数据源(连接池)
    	 * @return
    	 */
    	public static DataSource getDataSource(){
    		return dataSource;
    	}
    	
    	/**
    	 * 获得连接
    	 * @return
    	 * @throws SQLException 
    	 */
    	public static Connection getConnection() throws SQLException{
    		//1 从本地线程变量获得
    		Connection conn = local.get();
    		
    		//2 如果没有,从连接池获得,并添加到ThreadLocal中
    		if(conn == null){
    			//将从连接池中获得连接
    			conn = dataSource.getConnection();
    			//需保持本地
    			local.set(conn);
    		}
    		
    		//3 返回连接
    		return conn;
    	}
    }
    

     

源码地址: https://pan.quark.cn/s/d1f41682e390 miyoubiAuto 米游社每日米游币自动化Python脚本(务必使用Python3) 8更新:更换cookie的获取地址 注意:禁止在B站、贴吧、或各大论坛大肆传播! 作者已退游,项目不维护了。 如果有能力的可以pr修复。 小引一波 推荐关注几个非常可爱有趣的女孩! 欢迎B站搜索: @嘉然今天吃什么 @向晚大魔王 @乃琳Queen @贝拉kira 第三方库 食用方法 下载源码 在Global.py中设置米游社Cookie 运行myb.py 本地第一次运行时会自动生产一个文件储存cookie,请勿删除 当前仅支持单个账号! 获取Cookie方法 浏览器无痕模式打开 http://user.mihoyo.com/ ,登录账号 按,打开,找到并点击 按刷新页面,按下图复制 Cookie: How to get mys cookie 当触发时,可尝试按关闭,然后再次刷新页面,最后复制 Cookie。 也可以使用另一种方法: 复制代码 浏览器无痕模式打开 http://user.mihoyo.com/ ,登录账号 按,打开,找到并点击 控制台粘贴代码并运行,获得类似的输出信息 部分即为所需复制的 Cookie,点击确定复制 部署方法--腾讯云函数版(推荐! ) 下载项目源码和压缩包 进入项目文件夹打开命令行执行以下命令 xxxxxxx为通过上面方式或取得米游社cookie 一定要用双引号包裹!! 例如: png 复制返回内容(包括括号) 例如: QQ截图20210505031552.png 登录腾讯云函数官网 选择函数服务-新建-自定义创建 函数名称随意-地区随意-运行环境Python3....
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值