学习了解ThreadLocal

ThreadLocal

ThrteadLocal来提供线程内局部变量,这种变量在多线程访问时,可以保证每个线程变量互相独立.

​ 这种变量在线程生命周期内起作用,可以在不同组件中传递,类似于Javaweb 的Context 域.

常用方法:
方法说明
ThreadLocal<?>()创建对象
set(T value)设置当前线程绑定的局部变量
T get()获取变量
remove()移除与当前线程绑定的变量

Thread 与Synchornized的区别:

相同点:

两者都是用于处理多线程并发访问下的问题.

不同点:
Sychronized:

同步锁采用时间换空间的方式,只提供一份变量,让不同线程排队访问.

​ 是多个线程之间访问资源的同步.

ThreadLocal:

采用以空间换时间的方式,为每一个线程都提供一份变量副本,从而实现同时访问,互不干扰.

​ 多线程中每个线程之间数据相互隔离,具有高并发性.

ThreadLocal解决数据库连接并发的问题:

转账案列:

常规解决方案:使用synchorized关键字

注意:

1,service层和dao层连接对象保持一致,

2,每个线程Connection对象必须前后一致.

伪代码:

5,加锁:

synchronized(thsi.class){

​ 1,获取连接并开启事务:

​ Connection conn =JdbcUtils.getConnection();

​ coon.setAutoCommit(false);

​ 2,转账操作:

​ dao.out(outUser,money,conn);

​ 3,产生异常:

​ int i/0;

​ dao.in(inUser,money,conn);

​ 4 ,提交事务并关闭:

​ JdbcUtils.setCommitAndClose();

处理异常:

​ conn.rollback;

}

#####常规方案的缺陷:

1,connection对象从service层传递connection到dao层, 造成代码耦合度提高,

2,加锁会造成线程失去并发性,程序性能降低

使用ThreadLaocal解决方案:

1,直接获取当前线程绑定的conn对象:

  为空: 从连接池中获取,在将此连接绑定到当前线程.

2,在关闭连接或者回滚时,需要释放当前线程绑定的连接对象!

伪代码:

jdbcUtils:

常规获取连接方式:

datasource.getConnection();

使用threadlocal获取方式:

public class JdbcUtils {
    //ThreadLocal对象 : 将connection绑定在当前线程中
    private static final ThreadLocal<Connection> tl = new ThreadLocal();

    // c3p0 数据库连接池对象属性
    private static final ComboPooledDataSource ds = new ComboPooledDataSource();

    // 获取连接
    public static Connection getConnection() throws SQLException {
        //取出当前线程绑定的connection对象
        Connection conn = tl.get();
        if (conn == null) {
            //如果没有,则从连接池中取出
            conn = ds.getConnection();
            //再将connection对象绑定到当前线程中
            tl.set(conn);
        }
        return conn;
    }
    
    //关闭连接时需要释放当前线程绑定的连接对象:
    
    t1.remove();
    conn.close();
该方案的好处:
  1. 传递数据 :保存每个线程绑定的数据,在需要的地方可以直接获取, 避免参数直接传递带来的代码耦合问题
  2. 线程隔离 : 各线程之间的数据相互隔离却又具备并发性,避免同步方式带来的性能损失

ThreadLocal结构:

jdk早期设计:

每个ThreadLocal都创建一个Map,

key ->线程

value -> 线程变量

jdk8中的设计:

每个Thread都维护一个Map,

​ Thread{

//成员属性:

​ ThradLocalMap threadLocals;

}

key ->threadlocal实例

value -> 线程变量

设计好处:

1, 每一个map存储的entry减少,可以减少hash冲突.

2,当线程销毁时,ThreadLocalMap 也会随之销毁,减少内存使用.

ThreadLocal内部结构:
1.set():
/**
     * 设置当前线程对应的ThreadLocal的值
     *
     * @param value 将要保存在当前线程对应的ThreadLocal的值
     */
    public void set(T value) {
        // 获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取此线程对象中维护的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 判断map是否存在
        if (map != null)
            // 存在则调用map.set设置此实体entry
            map.set(this, value);
        else
            // 1)当前线程Thread 不存在ThreadLocalMap对象
            // 2)则调用createMap进行ThreadLocalMap对象的初始化
            // 3)并将 t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中
            createMap(t, value);
    }


    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
	 

	void createMap(Thread t, T firstValue) {
        //这里的this是调用此方法的threadLocal
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

A. 首先获取当前线程,并根据当前线程获取一个Map

B. 如果获取的Map不为空,则将参数设置到Map中(当前ThreadLocal的引用作为key)

C. 如果Map为空,则给该线程创建 Map,并设置初始值

2,get:
public T get() {
        // 获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取此线程对象中维护的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 如果此map存在
        if (map != null) {
            // 以当前的ThreadLocal 为 key,调用getEntry获取对应的存储实体e
            ThreadLocalMap.Entry e = map.getEntry(this);
            // 对e进行判空 
            if (e != null) {
                @SuppressWarnings("unchecked")
                // 获取存储实体 e 对应的 value值
                // 即为我们想要的当前线程对应此ThreadLocal的值
                T result = (T)e.value;
                return result;
            }
        }
        /*
        	初始化 : 有两种情况有执行当前代码
        	第一种情况: map不存在,表示此线程没有维护的ThreadLocalMap对象
        	第二种情况: map存在, 但是没有与当前ThreadLocal关联的entry
         */
        return setInitialValue();
    }

    /**
     * 初始化
     *
     * @return the initial value 初始化后的值
     */
    private T setInitialValue() {
        // 调用initialValue获取初始化的值
        // 此方法可以被子类重写, 如果不重写默认返回null
        T value = initialValue();
        // 获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取此线程对象中维护的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 判断map是否存在
        if (map != null)
            // 存在则调用map.set设置此实体entry
            map.set(this, value);
        else
            // 1)当前线程Thread 不存在ThreadLocalMap对象
            // 2)则调用createMap进行ThreadLocalMap对象的初始化
            // 3)并将 t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中
            createMap(t, value);
        // 返回设置的值value
        return value;
    }

A. 首先获取当前线程, 根据当前线程获取一个Map

B. 如果获取的Map不为空,则在Map中以ThreadLocal的引用作为key来在Map中获取对应的Entry e,否则转到D

C. 如果e不为null,则返回e.value,否则转到D

D. Map为空或者e为空,则通过initialValue函数获取初始值value,然后用ThreadLocal的引用和value作为firstKey和firstValue创建一个新的Map

总结: 先获取当前线程的 ThreadLocalMap 变量,如果存在则返回值,不存在则创建并返回初始值。

3,remove()
public void remove() {
        // 获取当前线程对象中维护的ThreadLocalMap对象
         ThreadLocalMap m = getMap(Thread.currentThread());
        // 如果此map存在
         if (m != null)
            // 存在则调用map.remove
            // 以当前ThreadLocal为key删除对应的实体entry
             m.remove(this);
            
     }
4.initialValue():
protected T initialValue() {
    return null;
}

此方法的第一次调用发生在,当线程通过get方法访问此线程的ThreadLocal值时

  • 除非线程先调用了set方法,在这种情况下,initialValue 才不会被这个线程调用。
  • 通常情况下,每个线程最多调用一次这个方法。
  • 这个方法仅仅简单的返回null
  • 如果程序员想ThreadLocal线程局部变量有一个除null以外的初始值,必须通过子类继承的方式去重写此方法,通常, 可以通过匿名内部类的方式实现

ThreadLocalMap源码分析

ThreadLcalMap是Thread的静态内部类

ThreadLcalMap{

​ initalCapacity :初始容量;

​ table Entry[] :存放entry的数组;

​ threshold: 数组扩容的阈值;

​ size: 数组里面的entry的个数;

}

Entry:

entry是**弱引用,**其目的是将ThreadLocal对象生命周期和线程生命周期绑定.

弱引用(weekReference):

垃圾回收器一旦发现只有弱引用的对象,不管当前内存是否足够都会回收他的内存.

  static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;

            Entry(ThreadLocal<?> var1, Object var2) {
                super(var1);
                this.value = var2;
            }
        }
内存分析图:

在这里插入图片描述

关于内存泄露的相关问题:

内存溢出memory overflow: 没有足够的内存提供给申请者使用.

内存泄漏 momory leak : 程序中动态分配的内存由于某种原因,内存未释放或者无法释放,造成内存的浪费,导致程序运行速度过慢,甚至系统崩溃等后果.内存泄露的堆积将导致内存溢出.

  • 内存泄露与key的弱引用还是强引用没有关系.

  • 出现内存引用的真实原因:

    • 1,没有手动删除entry
      • 解决:调用remove方法
    • 2,currentLocal任然运行:
      • 解决:ThreadLocalmap(entry)是Thread的一个属性,被当前线程所引用,只要当前线程结束,entry会被回收,从根源上避免内存泄漏.
  • 为什么还要使用弱引用?

    • 因为避免内存泄露的第二种方式不好控制,特别是使用线程池时候,线程结束不会被销毁.
    • 事实上在ThreadLocalMap中的get/set/remove方法中对key(threadLocal)为null,会将value也会设置为null.
  • 结论:

    使用完ThreadLocal,currentThread依然在运行状态下,弱应用的threadLocal会被回收,只要下次ThreadLocalMap调用set/get/map 任意方法,value会被清除,从而避免内存泄露.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值