线程安全(七)ThreadLocal和java的四种引用

系列文章目录

线程安全(一)java对象头分析以及锁状态
线程安全(二)java中的CAS机制
线程安全(三)实现方法sychronized与ReentrantLock(阻塞同步)
线程安全(四)Java内存模型与volatile关键字
线程安全(五)线程状态和线程创建
线程安全(六)线程池
线程安全(七)ThreadLocal和java的四种引用
线程安全(八)Semaphore
线程安全(九)CyclicBarrier
线程安全(十)AQS(AbstractQueuedSynchronizer)

0.前言

如果一个对象想要被回收,那么必须满足两个条件
没有任何引用(根可达算法)与GC。
引用的两种种情况
1循环引用 A里有B,B里有A(Spring框架里也要避免循环依赖)

DemoA a=new DemoA();
DemoB b=new DemoB(a);
a = new DemoA(b);

这种情况下,是否会被回收首先要看a和b是否可达,可达的话,只要一个不为null,则a,b都不被回收。且如果强制使对象为null,还有可能造成内存泄漏。
2.依赖 B里有A,b依赖a,这种把a置为null,也不能被回收,可能造成内存泄漏。

DemoA a=new DemoA();
DemoB b=new DemoB(a);

1.JAVA四种引用类型

1.1.强引用

处于可达状态,JVM不会回收,没有任何引用指向时,就被回收了,是内存泄漏的主要原因

1.2.软引用

SoftReference,垃圾回收机制运行,不会回收,内存一旦不够,就回收(-Xmx),常用于缓存

1.3.弱引用

WeakReference,垃圾回收机制一运行,就回收,
弱引用解决什么问题,就是单依赖

DemoA a=new DemoA();
WeakReference b=new WeakReference(a);
a=null;

DemoA只是被弱引用依赖,即弱可达,也就是说当前指向它的全都是弱引用,这时垃圾收集器会清除所有指向该对象的弱引用,这个弱可达对象标记回收。ThreadLocal中使用,后边详细说明。

1.4.虚引用

PhantomeReference,必须和引用队列联合使用。主要作用跟踪对象被垃圾回收的状态。
get不到,管理堆外内存,当对象被回收时,通过Queue可以检测到,然后清理堆外内存,nio

2.ThreadLocal

2.1.使用场景

ThreadLoca中填充的数据只属于当前线程,对其他线程是隔离的。
用途举例:
Spring的使用ThreadLoca的方式,来保证单个线程中的数据库操作使用的是同一个数据库连接。
TransactionSynchronizationManager

private static final ThreadLocal<Map<Object, Object>> resources =
			new NamedThreadLocal<>("Transactional resources");

	private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
			new NamedThreadLocal<>("Transaction synchronizations");

	private static final ThreadLocal<String> currentTransactionName =
			new NamedThreadLocal<>("Current transaction name");

2.2.使用方式

ThreadLocal<String> String= new ThreadLocal();
localName.set(“111");
String str = localName.get();
localName.remove();

一般会在Filter中,初始化ThreadLocal信息,加载一些需要在一起请求中到需要的变量,在中间使用是就可以直接使用了,场景:用户权限信息,流程状态信息等

2.3.源码解析

ThreadLocal的set方法

 public void set(T value) {
        Thread t = Thread.currentThread();
        // 获取ThreadLocalMap 
        ThreadLocal.ThreadLocalMap map = this.getMap(t);
        if (map != null) {
        	// 已有,ThreadLocalMap.set
            map.set(this, value);
        } else {
        	// 没有,ThreadLocalMap .create
            this.createMap(t, value);
        }
    }

每个线程Thread都维护了自己的threadLocals变量,数据实际存储在线程Thread的threadLocals变量里面。

ThreadLocalMap的初始化方法

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
	// 初始化
     ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

ThreadLocalMap getMap方法

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

ThreadLocalMap的构造方法

static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
·
            Entry(ThreadLocal<?> k, Object v) {
            	// 会调用父类构造方法ThreadLocal<?> k直接作为弱引用
                super(k);
                value = v;
            }
        }

ThreadLocalMap底层实际和HashMap很像,都有Entry[] table,区别是,HashMap有数组下还有链表或红黑树,它没有。他只会占据table中的一个位置。不会出现两个占据同一位置的可能

ThreadLocalMap的set方法

private void set(ThreadLocal<?> key, Object value) {
           Entry[] tab = table;
            int len = tab.length;
            // 计算table的位置
            int i = key.threadLocalHashCode & (len-1);
            // 不行的话,找下一个为空的值
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
				// 覆盖
                if (k == key) {
                    e.value = value;
                    return;
                }
                // 赋值
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

2.4.总结

在Java中,栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。实例在堆,但可见性只有线程可见
ThreadLocal在保存的时候会把自己当做Key存在ThreadLocalMap中,
如果ThreadLocal没有被外部强引用,GC时,ThreadLocal必被回收(因为是弱引用),ThreadLocalMap中的的一个key为null的Entry,而这个Entry无法被访问,线程如果一直使用,就会存在一条强引用链。
Thread Ref - > Thread -> ThreadLocalMap - > Entry -> value 永远无法回收而造成内存泄漏
调用get或者set方法会将key为null的这些Entry都删除,防止内存泄露。但是不一定使用,
将ThreadLocal变量定义成private static的,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露
每次使用完ThreadLocal,都调用它的remove()方法,清除数据

问题:为什么ThreadLocal用弱引用,内存溢出?
1.如果强引用,即使TL=null,但key依然指向ThreadLocal,所以内存会泄露,虚引用则不会
2.仍存在内存泄漏,ThreadLocal被回收,key值变为null,则导致value再也不会被访问到
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值