4、强软弱虚四种引用以及ThreadLocal的原理

ThreadLocal 线程局部变量

 demo:打印张三,但打印之前线程2 把值改成李四,最终打印李四了。。

import java.util.concurrent.TimeUnit;

public class ThreadLocal1 {
    volatile static Person p = new Person();

    public static void main(String[] args) {

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(p.name);
        }).start();

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            p.name = "lisi";
        }).start();
    }
}

class Person {
    String name = "zhangsan";
}

ThreadLocal 解决问题:

ThreadLocal是使用空间换时间,synchronized是使用时间换空间
比如在hibernate中session就存在与ThreadLocal中,避免synchronized的使用

import java.util.concurrent.TimeUnit;

public class ThreadLocal2 {
    //volatile static Person p = new Person();
    static ThreadLocal<Person> tl = new ThreadLocal<>();

    public static void main(String[] args) {
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 打印 null,如果在打印之前有值,应该是能获取到的
            System.out.println(tl.get());
        }).start();

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 在线程1 之前set 一个值
            tl.set(new Person());
        }).start();
    }
    static class Person {
        String name = "zhangsan";
    }
}

先看ThreadLocal 的set 源码:

当我们使用set 时候,它是new 一个map,map去set 一个值,key是this,value 是我们要set 的值

如果map 为空,就创建一个map,我们看这个createMap 方法

可以看到返回的threadLocals 接收的是ThreadLocalMap

这个ThreadLocalMap,是在Thread 类里面的,

所以得出结论:Thread里面的currentThread里面的map ,是设置到了当前线程里面的一个map,所以别的线程读不到

 四种引用

 先看这个类:

public class M {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalize");
    }
}

只是想说一下,当每次看到这个方法时候,说明这个对象被回收了。

 强引用(普通引用)new 一个对象,放在垃圾回收器,不会被回收,因为有引用指向它,所以不会被回收,没有引用指向,就会被回收。

import java.io.IOException;

public class T01_NormalReference {
    public static void main(String[] args) throws IOException {
        M m = new M();
        m = null;   // 改为没有引用
        System.gc(); //DisableExplicitGC
        // 阻塞当前线程
        System.in.read();
    }
}

软引用 软引用非常适合缓存使用,空间够就不回收

软引用是用来描述一些还有用但并非必须的对象。
对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行第二次回收。
如果这次回收还没有足够的内存,才会抛出内存溢出异常。设置堆内存大小  -Xmx20M

import java.lang.ref.SoftReference;

public class T02_SoftReference {
    public static void main(String[] args) {
        SoftReference<byte[]> m = new SoftReference<>(new byte[1024*1024*10]);
        //m = null;
        System.out.println(m.get());
        System.gc();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 如果被回收,打印 空值
        System.out.println(m.get());

        //再分配一个数组,heap将装不下,这时候系统会垃圾回收,先回收一次,如果不够,会把软引用干掉
        byte[] b = new byte[1024*1024*15];
        System.out.println(m.get());
    }
}

弱引用 遭到gc就会回收

import java.lang.ref.WeakReference;

public class T03_WeakReference {
    public static void main(String[] args) {
        WeakReference<M> m = new WeakReference<>(new M());

        System.out.println(m.get());
        System.gc();
        System.out.println(m.get());

        ThreadLocal<M> tl = new ThreadLocal<>();
        tl.set(new M());
        tl.remove();    // 使用虚引用时候,不用了以后,一定要手动remove 掉
    }
}

虚引用

一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,
也无法通过虚引用来获取一个对象的实例。
为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
虚引用和弱引用对关联对象的回收都不会产生影响,如果只有虚引用活着弱引用关联着对象,
那么这个对象就会被回收。它们的不同之处在于弱引用的get方法,虚引用的get方法始终返回null,
弱引用可以使用ReferenceQueue,虚引用必须配合ReferenceQueue使用。
jdk中直接内存的回收就用到虚引用,由于jvm自动内存管理的范围是堆内存,
而直接内存是在堆内存之外(其实是内存映射文件,自行去理解虚拟内存空间的相关概念),
所以直接内存的分配和回收都是有Unsafe类去操作,java在申请一块直接内存之后,
会在堆内存分配一个对象保存这个堆外内存的引用,
这个对象被垃圾收集器管理,一旦这个对象被回收,
相应的用户线程会收到通知并对直接内存进行清理工作。
事实上,虚引用有一个很重要的用途就是用来做堆外内存的释放,
DirectByteBuffer就是通过虚引用来实现堆外内存的释放的。

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.LinkedList;
import java.util.List;

public class T04_PhantomReference {
    private static final List<Object> LIST = new LinkedList<>();
    private static final ReferenceQueue<M> QUEUE = new ReferenceQueue<>();

    public static void main(String[] args) {
        // 因为虚引用是堆外的内存,所以要使用队列,用来标记回收,然后清理堆外内存
        PhantomReference<M> phantomReference = new PhantomReference<>(new M(), QUEUE);

        new Thread(() -> {
            while (true) {
                LIST.add(new byte[1024 * 1024]);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    Thread.currentThread().interrupt();
                }
                // 因为虚引用一直被回收,所以打印空
                System.out.println(phantomReference.get());
            }
        }).start();

        new Thread(() -> {
            while (true) {
                // 一直检测队列里有没有值,有值说明虚引用被回收了
                Reference<? extends M> poll = QUEUE.poll();
                if (poll != null) {
                    System.out.println("--- 虚引用对象被jvm回收了 ---- " + poll);
                }
            }
        }).start();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 

posted @ 2021-02-22 15:49  aBiu--  阅读(103)  评论(0编辑  收藏  举报