ThreadLocal源码

四种引用类型:

1.强引用: 即最常见的引用
Object o=new Object();

比如此时 o这个对象就指向一个Object对象的强引用

如果一个对象具有强引用,那就类似于必不可少的物品,不会被垃圾回收器回收。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不回收这种对象。

2.软引用:软引用是用来描述一些有用但并不是必需的对象,在Java中用java.lang.ref.SoftReference类来表示。

SoftReferency<byte[]> m=new SoftReferency<>(new byte[1024102410]);

我们如何得到SoftReferency存储的软引用对象呢? : m.get()

对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象。因此,这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等。

3.弱引用
弱引用也是用来描述非必需对象的,在java中,用java.lang.ref.WeakReference类来表示。

WeakReference wr = new WeakReference(new String("hello"));

当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象

4.虚引用
虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。在java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收的活动

ReferenceQueue queue = new ReferenceQueue();
PhantomReference pr = new PhantomReference(new String("hello"), queue);

虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之 关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

我们调用pr.get()发现结果为Null,拿不到弱引用对象!

虚引用被用来管理直接内存!(JVM中提供一个指针直接指向操作系统中的内存而不用拷贝操作系统中的内存)

ThreadLocal

ThreadLocal典型应用:
在Spring中有一个@transactional 注解开启事务,在事务中方法调用 A方法调用B方法 B方法调用C方法 C方法调用D方法 必须确保使用的是同一个connection,spring就采用threadlocal传递该connection(为什么不能设成全局的? 只是在该事务中使用同一个connection 不希望所有跟数据库有关的都使用这一个connection)

举个例子:

static ThreadLocal<String> t = new ThreadLocal<>();
        new Thread(()->{
            
            t.set("hellp");
        }).start();
        new Thread(()->{
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(t.get());
        }).start();

我们发现尽管是同一个ThreadLocal对象,我们还是取不到其他线程存放的数据!

我们看下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);
        }
    }

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

1.每个Thread对象都有一个threadlocals(ThreadLocalMap对象),我们先获取当前线程的ThreadLocalMap对象
2.ThreadLocalMap对象的 key是我们的ThreadLocal对象,value是我们存放的数据 换句话说 每个ThreadLocal对象只能存放一条记录,否则就会被替换掉(因为同一个key)
3.ThreadLocalMap的set方法就是new了一个Entry对象,Entry的key是我们的ThreadLocal对象,value是我们存放的数据,但需要注意的是这个Entry是WeakReference的子类

static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
               //调用WeakReference的构造器
                super(k);
                value = v;
            }
        }

我们可以看到此时 key(ThreadLocal对象)是一个弱引用

为什么要用弱引用?

如果key是一个强引用,那么由于线程中存在的ThreadLocalMap中有对该ThreadLocal的引用,只要有线程存在ThreadLocalMap(有key为ThreadLocal),该ThreadLocal对象就永远回收不了,产生内存泄漏。如果该Entry中key为弱引用,我们只需要满足ThreadLocal t = new ThreadLocal<>();这个强引用的回收条件(置空)垃圾回收器即使看到弱引用也会把ThreadLocal对象回收。但是需要注意的是,此时该Key(即ThreadLocal对象)被回收了,key变为null,value永远获取不到,产生内存泄露!我们看ThreadLocal源码,发现他在set/get时会把key为Null的map记录删掉,尽管如此我们在不需要某条map记录时最好还是手动remove()一下

ThreadLocalMap哈希冲突

和HashMap采用链表+红黑树解决哈希冲突的方式不同,ThreadLocalMap采用线性探测的方式解决哈希冲突,即当一个key(ThreadLocal)所在位置处有其他对象,他会探测下一个位置,直到有空的地址后插入。(空间不够会扩容的)

posted @ 2021-07-18 23:44  刚刚好。  阅读(61)  评论(0)    收藏  举报