多线程进阶学习09------ThreadLocal详解

ThreadLocal:提供线程的局部变量,对于线程共享变量如果使用ThreadLocal则无需加锁,更省事省心。

ThreadLocal本地线程变量,线程自带的变量副本(实现了每一个线程副本都有一个专属的本地变量,主要解决的就是让每一个线程绑定自己的值,自己用自己的,不跟别人争抢。通过使用ThreadLocal#get()和set()方法,获取默认值或将其值更改为当前线程所存的副本的值从而避免了线程安全的问题)

synchronized到ThreadLocal的演进

①. synchronized或者lock,有个管理员,好比,现在大家签到,多个同学(线程),但是只有一只笔,只能同一个时间,只有一个线程(同学)签到,加锁(同步机制是以时间换空间,执行时间不一样,类似于排队)
在这里插入图片描述
②. ThreadLocal,人人有份,每个同学手上都有一支笔,自己用自己的,不用再加锁来维持秩序(同步机制是以空间换时间,为每一个线程都提供了一份变量的副本,从而实现同时访问,互不干扰同时访问,肯定效率高啊)

在这里插入图片描述

访问这个变量的每个线程都会有这个变量的本地副本

通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。如果想实现每一个线程都有自己的专属本地变量该如何解决呢?
JDK 中提供的ThreadLocal类正是为了解决这样的问题。 ThreadLocal类主要解决的就是让每个线程绑定它自己局部的值。
如果内存当中存在一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是ThreadLocal变量名的由来。

在这里插入图片描述
可以使用 get() 和 set() 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。

应用案例一:

【强制】SimpleDateFormat 是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类


import java.text.SimpleDateFormat;
import java.util.Random;

public class ThreadLocalExample implements Runnable{
    private static final ThreadLocal<SimpleDateFormat> formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmm"));

    public static void main(String[] args) throws InterruptedException {
        ThreadLocalExample obj = new ThreadLocalExample();
        for(int i=0 ; i<10; i++){
            Thread t = new Thread(obj, ""+i);
            Thread.sleep(new Random().nextInt(1000));
            t.start();
        }
    }

    @Override
    public void run() {
        System.out.println("Thread Name= "+Thread.currentThread().getName()+" default Formatter = "+formatter.get().toPattern());
        try {
            Thread.sleep(new Random().nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //formatter pattern is changed here by thread, but it won't reflect to other threads
        formatter.set(new SimpleDateFormat());

        System.out.println("Thread Name= "+Thread.currentThread().getName()+" formatter = "+formatter.get().toPattern());
    }

}
//输出
Thread Name= 0 default Formatter = yyyyMMdd HHmm
Thread Name= 0 formatter = yy-M-d ah:mm
Thread Name= 1 default Formatter = yyyyMMdd HHmm
Thread Name= 2 default Formatter = yyyyMMdd HHmm
Thread Name= 1 formatter = yy-M-d ah:mm
Thread Name= 3 default Formatter = yyyyMMdd HHmm
Thread Name= 2 formatter = yy-M-d ah:mm
Thread Name= 4 default Formatter = yyyyMMdd HHmm
Thread Name= 3 formatter = yy-M-d ah:mm
Thread Name= 4 formatter = yy-M-d ah:mm
Thread Name= 5 default Formatter = yyyyMMdd HHmm
Thread Name= 5 formatter = yy-M-d ah:mm
Thread Name= 6 default Formatter = yyyyMMdd HHmm
Thread Name= 6 formatter = yy-M-d ah:mm
Thread Name= 7 default Formatter = yyyyMMdd HHmm
Thread Name= 7 formatter = yy-M-d ah:mm
Thread Name= 8 default Formatter = yyyyMMdd HHmm
Thread Name= 9 default Formatter = yyyyMMdd HHmm
Thread Name= 8 formatter = yy-M-d ah:mm
Thread Name= 9 formatter = yy-M-d ah:mm

从输出中可以看出,Thread-0 已经改变了 formatter 的值,但仍然是 thread-2 默认格式化程序与初始化值相同,其他线程也一样。
上面有一段代码用到了创建 ThreadLocal 变量的那段代码用到了 Java8 的知识,因为 ThreadLocal 类在 Java 8 中扩展,使用一个新的方法withInitial(),将 Supplier 功能接口作为参数。所以它等于下面这段代码。

private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue()
        {
            return new SimpleDateFormat("yyyyMMdd HHmm");
        }
    };

应用案例二:

每个销售员可以出售多少套房子
①. 因为每个Thread内有自己的实例副本且该副本只由当前线程自己使用
②. 既然其他Thread不可访问,那就不存在多线程共享的问题
③. 统一设置初始值,但是每个线程对这个值的修改都是各自线程互相独立的

package com.bilibili.juc.tl;

import lombok.Getter;
import sun.font.FontRunIterator;

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

class House //资源类
{
    int saleCount = 0;
    public synchronized void saleHouse()
    {
        ++saleCount;
    }

    /*ThreadLocal<Integer> saleVolume = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue()
        {
            return 0;
        }
    };*/
    ThreadLocal<Integer> saleVolume = ThreadLocal.withInitial(() -> 0);
    public void saleVolumeByThreadLocal()
    {
        saleVolume.set(1+saleVolume.get());
    }
}

/**
 * @auther zzyy
 * @create 2021-12-31 15:46
 *
 * 需求1: 5个销售卖房子,集团高层只关心销售总量的准确统计数。
 *
 * 需求2: 5个销售卖完随机数房子,各自独立销售额度,自己业绩按提成走,分灶吃饭,各个销售自己动手,丰衣足食
 *
 *
 */
public class ThreadLocalDemo
{
    public static void main(String[] args) throws InterruptedException
    {

        House house = new House();

        for (int i = 1; i <=5; i++) {
            new Thread(() -> {
                int size = new Random().nextInt(5)+1;
                try {
                    for (int j = 1; j <=size; j++) {
                        house.saleHouse();
                        house.saleVolumeByThreadLocal();
                    }
                    System.out.println(Thread.currentThread().getName()+"\t"+"号销售卖出:"+house.saleVolume.get());
                } finally {
                    house.saleVolume.remove();
                }
            },String.valueOf(i)).start();
        };

        //暂停毫秒
        try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println(Thread.currentThread().getName()+"\t"+"共计卖出多少套:saleCount "+house.saleCount);
        System.out.println(Thread.currentThread().getName()+"\t"+"共计卖出多少套:saleVolume "+house.saleVolume.get());
    }
}
//输出
D:\java8u211\jdk\bin\java.exe "-javaagent:D:\Program Files\IntelliJ IDEA 2020.3.1\lib\idea_rt.jar=57334:D:\Program Files\IntelliJ IDEA 2020.3.1\bin" -Dfile.encoding=GBK -classpath D:\java8u211\jdk\jre\lib\charsets.jar;D:\java8u211\jdk\jre\lib\deploy.jar;D:\java8u211\jdk\jre\lib\ext\access-bridge-64.jar;D:\java8u211\jdk\jre\lib\ext\cldrdata.jar;D:\java8u211\jdk\jre\lib\ext\dnsns.jar;D:\java8u211\jdk\jre\lib\ext\jaccess.jar;D:\java8u211\jdk\jre\lib\ext\jfxrt.jar;D:\java8u211\jdk\jre\lib\ext\localedata.jar;D:\java8u211\jdk\jre\lib\ext\nashorn.jar;D:\java8u211\jdk\jre\lib\ext\sunec.jar;D:\java8u211\jdk\jre\lib\ext\sunjce_provider.jar;D:\java8u211\jdk\jre\lib\ext\sunmscapi.jar;D:\java8u211\jdk\jre\lib\ext\sunpkcs11.jar;D:\java8u211\jdk\jre\lib\ext\zipfs.jar;D:\java8u211\jdk\jre\lib\javaws.jar;D:\java8u211\jdk\jre\lib\jce.jar;D:\java8u211\jdk\jre\lib\jfr.jar;D:\java8u211\jdk\jre\lib\jfxswt.jar;D:\java8u211\jdk\jre\lib\jsse.jar;D:\java8u211\jdk\jre\lib\management-agent.jar;D:\java8u211\jdk\jre\lib\plugin.jar;D:\java8u211\jdk\jre\lib\resources.jar;D:\java8u211\jdk\jre\lib\rt.jar;E:\Project\source-bulldozer\JavaConcurrent\target\classes;E:\dev_dir_temp\apache-maven-3.3.9\respository\org\springframework\boot\spring-boot-starter-web\2.5.8\spring-boot-starter-web-2.5.8.jar;E:\dev_dir_temp\apache-maven-3.3.9\respository\org\springframework\boot\spring-boot-starter\2.5.8\spring-boot-starter-2.5.8.jar;E:\dev_dir_temp\apache-maven-3.3.9\respository\org\springframework\boot\spring-boot\2.5.8\spring-boot-2.5.8.jar;E:\dev_dir_temp\apache-maven-3.3.9\respository\org\springframework\boot\spring-boot-autoconfigure\2.5.8\spring-boot-autoconfigure-2.5.8.jar;E:\dev_dir_temp\apache-maven-3.3.9\respository\org\springframework\boot\spring-boot-starter-logging\2.5.8\spring-boot-starter-logging-2.5.8.jar;E:\dev_dir_temp\apache-maven-3.3.9\respository\ch\qos\logback\logback-classic\1.2.9\logback-classic-1.2.9.jar;E:\dev_dir_temp\apache-maven-3.3.9\respository\ch\qos\logback\logback-core\1.2.9\logback-core-1.2.9.jar;E:\dev_dir_temp\apache-maven-3.3.9\respository\org\apache\logging\log4j\log4j-to-slf4j\2.17.0\l
4	号销售卖出:2
1	号销售卖出:5
2	号销售卖出:5
3	号销售卖出:2
5	号销售卖出:2
main	共计卖出多少套:saleCount 16
main	共计卖出多少套:saleVolume 0

Process finished with exit code 0

ThreadLocal 源码

Thread类源码#仅仅定义HashMap

public class Thread implements Runnable {
 ......
//与此线程有关的ThreadLocal值。由ThreadLocal类维护
ThreadLocal.ThreadLocalMap threadLocals = null;

//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
 ......
}

从上面Thread类 源代码可以看出Thread 类中有一个 threadLocals 和 一个 inheritableThreadLocals 变量,它们都是 ThreadLocalMap 类型的变量,我们可以把 ThreadLocalMap 理解为ThreadLocal 类实现的定制化的 HashMap。

默认情况下这两个变量都是 null,只有当前线程调用 ThreadLocal 类的 set或get方法时才创建它们。

ThreadLocal类源码#初始化HashMap

但为什么不通过Thread类调用getset,而是通过ThreadLocal 自己比如ThreadLocal formatter.set(value).getset呢?

●Thread类里面只是声明了ThreadLocalMap 但并未初始化
●ThreadLocal 在自己的getset方法内初始化了ThreadLocalMap ,并且传递了最终的变量值value,以及对value进行装填

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;
    }

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }


    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();
    }   

    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;
    }

    protected T initialValue() {
        return null;
    }

1、最后, ThreadLocalMap 的key是ThreadLocal ,多个线程Thread可以共用一个ThreadLocal 作为key,而不同的value才是最终的值。
2、相同的key并不会产生ThreadLocalMap当中Entry覆盖的问题,因为多个Thread并不会公用一个ThreadLocalMap,而是有几个Thread就会对应几个ThreadLocalMap
还是这张图:我们加深下对上述最后两点的理解
在这里插入图片描述
ThreadLocal的静态内部类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;
            }
        }

ThreadLocal可能造成的问题

【强制】必须回收自定义的ThreadLocal变量,尤其在线程池场景下,线程经常会被复用,
如果不清理自定义的 ThreadLocal变量,可能会影响后续业务逻辑和造成内存泄露等问题。尽量在代理中使用try-finally块进行回收。 正例:
objectThreadLocal.set(userInfo);
try {
// ...
} finally {
objectThreadLocal.remove();
}

读懂阿里开发规范,由于线程池场景下的线程经常会被复用
●结果:影响后续业务逻辑
●结果:或造成内存泄露等问题

后续业务逻辑混乱问题

package com.bilibili.juc.tl;


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

class MyData
{
    ThreadLocal<Integer> threadLocalField = ThreadLocal.withInitial(() -> 0);
    public void add()
    {
        threadLocalField.set(1 + threadLocalField.get());
    }
}

/**
 * @auther zzyy
.【强制】必须回收自定义的 ThreadLocal 变量,尤其在线程池场景下,线程经常会被复用,如果不清理
自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题。尽量在代理中使用
try-finally 块进行回收。
 */
public class ThreadLocalDemo2
{
    public static void main(String[] args) throws InterruptedException
    {
        MyData myData = new MyData();

        ExecutorService threadPool = Executors.newFixedThreadPool(3);

        try
        {
            for (int i = 0; i < 10; i++) {
                threadPool.submit(() -> {
                    try {
                        Integer beforeInt = myData.threadLocalField.get();
                        myData.add();
                        Integer afterInt = myData.threadLocalField.get();
                        System.out.println(Thread.currentThread().getName()+"\t"+"beforeInt:"+beforeInt+"\t afterInt: "+afterInt);
                    } finally {
//                        myData.threadLocalField.remove();
                    }
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            threadPool.shutdown();
        }

    }
}

//输出
D:\java8u211\jdk\bin\java.exe "-javaagent:D:\Program Files\IntelliJ IDEA 2020.3.1\lib\idea_rt.jar=57644:D:\Program Files\IntelliJ IDEA 2020.3.1\bin" -Dfile.encoding=GBK -classpath D:\java8u211\jdk\jre\lib\charsets.jar;D:\java8u211\jdk\jre\lib\deploy.jar;D:\java8u211\jdk\jre\lib\ext\access-bridge-64.jar;D:\java8u211\jdk\jre\lib\ext\cldrdata.jar;D:\java8u211\jdk\jre\lib\ext\dnsns.jar;D:\java8u211\jdk\jre\lib\ext\jaccess.jar;D:\java8u211\jdk\jre\lib\ext\jfxrt.jar;D:\java8u211\jdk\jre\lib\ext\localedata.jar;D:\java8u211\jdk\jre\lib\ext\nashorn.jar;D:\java8u211\jdk\jre\lib\ext\sunec.jar;D:\java8u211\jdk\jre\lib\ext\sunjce_provider.jar;D:\java8u211\jdk\jre\lib\ext\sunmscapi.jar;D:\java8u211\jdk\jre\lib\ext\sunpkcs11.jar;D:\java8u211\jdk\jre\lib\ext\zipfs.jar;D:\java8u211\jdk\jre\lib\javaws.jar;D:\java8u211\jdk\jre\lib\jce.jar;D:\java8u211\jdk\jre\lib\jfr.jar;D:\java8u211\jdk\jre\lib\jfxswt.jar;D:\java8u211\jdk\jre\lib\jsse.jar;D:\java8u211\jdk\jre\lib\management-agent.jar;D:\java8u211\jdk\jre\lib\plugin.jar;D:\java8u211\jdk\jre\lib\resources.jar;D:\java8u211\jdk\jre\lib\rt.jar;E:\Project\source-bulldozer\JavaConcurrent\target\classes;E:\dev_dir_temp\apache-maven-3.3.9\respository\org\springframework\boot\spring-boot-starter-web\2.5.8\spring-boot-starter-web-2.5.8.jar;E:\dev_dir_temp\apache-maven-3.3.9\respository\org\springframework\boot\spring-boot-starter\2.5.8\spring-boot-starter-2.5.8.jar;E:\dev_dir_temp\apache-maven-3.3.9\respository\org\springframework\boot\spring-boot\2.5.8\spring-boot-2.5.8.jar;E:\dev_dir_temp\apache-maven-3.3.9\respository\org\springframework\boot\spring-boot-autoconfigure\2.5.8\spring-boot-autoconfigure-2.5.8.jar;E:\dev_dir_temp\apache-maven-3.3.9\respository\org\springframework\boot\spring-boot-starter-logging\2.5.8\spring-boot-starter-logging-2.5.8.jar;E:\dev_dir_temp\apache-maven-3.3.9\respository\ch\qos\logback\logback-classic\1.2.9\logback-classic-1.2.9.jar;E:\dev_dir_temp\apache-maven-3.3.9\respository\ch\qos\logback\logback-core\1.2.9\logback-core-1.2.9.jar;E:\dev_dir_temp\apache-maven-3.3.9\respository\org\apache\logging\log4j\log4j-to-slf4j\2.17.0\l
pool-1-thread-3	beforeInt:0	 afterInt: 1
pool-1-thread-2	beforeInt:0	 afterInt: 1
pool-1-thread-1	beforeInt:0	 afterInt: 1
pool-1-thread-3	beforeInt:1	 afterInt: 2
pool-1-thread-1	beforeInt:1	 afterInt: 2
pool-1-thread-2	beforeInt:1	 afterInt: 2
pool-1-thread-1	beforeInt:2	 afterInt: 3
pool-1-thread-1	beforeInt:3	 afterInt: 4
pool-1-thread-3	beforeInt:2	 afterInt: 3
pool-1-thread-2	beforeInt:2	 afterInt: 3

Process finished with exit code 0

内存泄露问题

每个Thread都有一个ThreadLocal.ThreadLocalMap类型的HashMap,该Map的key为ThreadLocal实例,他是一个弱引用,我们清楚弱引用有利于GC回收。

当ThreadLocal作为Map的key为null的时候,GC就会回收这一部分空间,但是value却不一定能够被回收,因为对于线程池当中的线程会被复用线程对象迟迟不会结束,value还与Current Thread存在一个强引用关系,如下图所示:

在这里插入图片描述
ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用。所以,如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。这样一来,ThreadLocalMap 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话,value 永远无法被 GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap 实现中已经考虑了这种情况,使用完 ThreadLocal方法后最好手动调用remove()方法会清理掉 key 为 null 的记录。

弱引用介绍:如果一个对象只具有弱引用,那就类似于可有可无的生活用品。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。

为什么用弱引用?
ThreadLocal为每一个线程提供了一个私有的变量,这个现象是依托于ThreadLocalMap来实现的,map中的每一个Entry的key都是指向了一个threadlocal,这是一个弱引用,当外界对指向threadlocal的强引用回收之后,就说明这个threadlocal就没用了,但是此时还有map中的key也指向了它,若是这个key是一个强引用,那么我们就无法对threadlocal进行回收,就有可能造成一个内存泄漏的问题,所以使用了弱引用来解决这个问题,只有弱引用指向的对象,在下次垃圾回收时就会被回收

强软弱虚四大引用扩展讲解

强引用:最常见

当内存不足,JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM也不会对该对象进行回收,机器死了都不收。

强引用是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象。 在Java 中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。 当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的, 即使该对象以后永远都不会被用到,JVM也不会回收。

因此强引用是造成Java内存泄漏的主要原因之一。

对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null一般认为才是可以被垃圾收集的了(当然具体回收时机还是要看垃圾收集策略)。

package com.bilibili.juc.tl;

import java.lang.ref.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

class MyObject
{
    //这个方法一般不用复写,我们只是为了教学给大家演示案例做说明
    @Override
    protected void finalize() throws Throwable
    {
        // finalize的通常目的是在对象被不可撤销地丢弃之前执行清理操作。
        System.out.println("-------invoke finalize method~!!!");
    }
}


/**
 * @auther zzyy
 */
public class ReferenceDemo
{
    public static void main(String[] args)
    {
        MyObject myObject = new MyObject();
        System.out.println("gc before: "+myObject);

        myObject = null;
        System.gc();//人工开启GC,一般不用

        //暂停毫秒
        try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println("gc after: "+myObject);

    }
}

软引用:看情况

软引用是一种相对强引用弱化了一些的引用,需要用java.lang.ref.SoftReference类来实现,可以让对象豁免一些垃圾收集。
对于只有软引用的对象来说:
●当系统内存充足时它 不会 被回收
●当系统内存不足时它 会被回收。
软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收!

在这里插入图片描述

package com.bilibili.juc.tl;

import java.lang.ref.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

class MyObject
{
    //这个方法一般不用复写,我们只是为了教学给大家演示案例做说明
    @Override
    protected void finalize() throws Throwable
    {
        // finalize的通常目的是在对象被不可撤销地丢弃之前执行清理操作。
        System.out.println("-------invoke finalize method~!!!");
    }
}


/**
 * @auther zzyy
 */
public class ReferenceDemo
{
    public static void main(String[] args)
        SoftReference<MyObject> softReference = new SoftReference<>(new MyObject());
        //System.out.println("-----softReference:"+softReference.get());

        System.gc();
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("-----gc after内存够用: "+softReference.get());

        try
        {
            byte[] bytes = new byte[20 * 1024 * 1024];//20MB对象
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            System.out.println("-----gc after内存不够: "+softReference.get());
        }

    }


}

假如有一个应用需要读取大量的本地图片 :
我 如果每次读取图片都从硬盘读取则会严重影响性能,

如果一次性全部加载到内存中又可能造成内存溢出。
此时使用软引用可以解决这个问题。 设计思路是 : 用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时, JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题。

Map<StringSoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();

弱引用:无法豁免

弱引用需要用java.lang.ref.WeakReference类来实现,它比软引用的生存期更短,

对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象占用的内存。

package com.bilibili.juc.tl;

import java.lang.ref.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

class MyObject
{
    //这个方法一般不用复写,我们只是为了教学给大家演示案例做说明
    @Override
    protected void finalize() throws Throwable
    {
        // finalize的通常目的是在对象被不可撤销地丢弃之前执行清理操作。
        System.out.println("-------invoke finalize method~!!!");
    }
}


/**
 * @auther zzyy
 */
public class ReferenceDemo
{
    public static void main(String[] args)
    {
        WeakReference<MyObject> weakReference = new WeakReference<>(new MyObject());
        System.out.println("-----gc before 内存够用: "+weakReference.get());

        System.gc();
        //暂停几秒钟线程
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println("-----gc after 内存够用: "+weakReference.get());
    }
}

虚引用

  • 引用队列:虚引用必须和引用队列 (ReferenceQueue)联合使用 虚引用需要java.lang.ref PhantomReference类来实现,顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的 生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用不能单独使用也不 能通过它访问对象,虚引用必须和引用队列 (ReferenceQueue)联合使用。 对象被回收之后,有东西将被装入引用队列 (ReferenceQueue)中。gc之后!!!
  • 总是返回null:PhantomReference的get方法总是返回null 虚引用的主要作用是跟踪对象被垃圾回收的状态。 PhantomReference的get方法总是返回null,因此无法访问对应的引用对象
  • 通知机制:虚引用仅仅是提供了一种确保对象被 finalize以后,做某些事情的通知机制。处理监控通知使用,换句话说,设置虚引用关联对象的唯一目的,就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步的处理,用 来实现比finalize机制更灵活的回收操作

在这里插入图片描述

package com.bilibili.juc.tl;

import java.lang.ref.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

class MyObject
{
    //这个方法一般不用复写,我们只是为了教学给大家演示案例做说明
    @Override
    protected void finalize() throws Throwable
    {
        // finalize的通常目的是在对象被不可撤销地丢弃之前执行清理操作。
        System.out.println("-------invoke finalize method~!!!");
    }
}


/**
 * @auther zzyy
 */
public class ReferenceDemo
{
    public static void main(String[] args)
    {
        MyObject myObject = new MyObject();
        ReferenceQueue<MyObject> referenceQueue = new ReferenceQueue<>();
        PhantomReference<MyObject> phantomReference = new PhantomReference<>(myObject,referenceQueue);
        //System.out.println(phantomReference.get());

        List<byte[]> list = new ArrayList<>();

        new Thread(() -> {
            while (true){
                list.add(new byte[1 * 1024 * 1024]);
                try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
                System.out.println(phantomReference.get()+"\t"+"list add ok");
            }
        },"t1").start();

        new Thread(() -> {
            while (true){
                Reference<? extends MyObject> reference = referenceQueue.poll();
                if(reference != null){
                    System.out.println("-----有虚对象回收加入了队列");
                    break;
                }
            }
        },"t2").start();

    }
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/4910.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

FastReport .NET 2023.2.4 Crack

FastReport .NET Reporting and documents creation library for .NET 7 FastReport .NET适用于 .NET 7、.NET Core、Blazor、ASP.NET、MVC 和 Windows Forms 的全功能报告库。它可以在微软视觉工作室 2022 和 JetBrains Rider 中使用。 利用 .NET 7、.NET Core、Blazor、ASP.N…

React:九、组件的生命周期

1.生命周期的理解 组件从创建到死亡它会经历一些特定的阶段。React组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。我们在定义组件时&#xff0c;会在特定的生命周期回调函数中&#xff0c;做特定的工作。2.生命周期小案例 <!DOCTYPE html> <html…

操作系统权限维持(十五)之Linux系统-inetd远程后门

系列文章 操作系统权限维持&#xff08;一&#xff09;之Windows系统-粘贴键后门 操作系统权限维持&#xff08;二&#xff09;之Windows系统-克隆账号维持后门 操作系统权限维持&#xff08;三&#xff09;之Windows系统-启动项维持后门 操作系统权限维持&#xff08;四&…

leaflet加载GPX文件,第2种图形显示方法(119)

第119个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中加载GPX文件,将图形显示在地图上,这是另外一种方式,看前一种方式请从目录中查找。GPX文件是以GPS数据交换格式保存的GPS数据文件,是一种通用的地图信息文件,可以被众多GPS应用和Web服务更轻松地导入和…

Jenksin pipeline: 全局变量 和 input中的局部变量

在jenkins的流水线中全局变量的定义基本有两种实现方法&#xff1a;参数化构建过程中定义&#xff0c;流水线中直接定义 参数化构建过程中定义 流水线中直接定义(Jenkins 2.x支持代码及即服务) 可以用流水线生成 在配置parameters后&#xff0c;往往需要先构建一遍&#…

耗时 24 小时整理了网络安全学习路线,非常详细!

前言上次发的文章【都2023年了&#xff0c;还在问网络安全怎么入门】很多小伙伴在评论区回复不知道怎么学习&#xff0c;我也反思了一下&#xff0c;确实没写到学习方法和路线&#xff0c;所以这一期就出一一个怎么学习网络安全的学习路线和方法&#xff0c;觉得有用的话三连收…

软件测试入门简单么?入行后如何做职业规划

软件测试的确是入门相对简单的一个学科&#xff0c;他们不常写代码&#xff0c;主要去检查代码&#xff0c;是不是出现了漏洞、程序是否能运行下去&#xff1f;那这部分程序员就是做软件测试。 这个类别没有做Java难没有大数据那么复杂&#xff0c;但还可以拿到程序员的高薪。…

一招解决macOS12 CleanMyMac闪退

距全新的macOS 12 Monterey正式版发布已经过去了快6个月&#xff0c;macOS Monterey 12 新增了同播共享功能、 Apple Music 声控方案、“数字遗产”计划、“照片”中重新设计的“回忆”&#xff0c;以及针对 Mac 的其他功能和错误修复等大量更新和改进。 很多Mac用户也已经升级…

【模板】树状数组

目录&#xff1a; 单点修改&#xff0c;区间查询&#xff1a; 题目描述&#xff1a; lowbit()运算&#xff1a; 插入、修改单点数据&#xff1a; 计算前缀和&#xff1a; 完整代码&#xff1a; 区间修改&#xff0c;单点查询&#xff1a; 计算差分数组&#xff1a; 计算每个点的…

Node.js 入门

转载请注明出处&#xff0c;点击此处 查看更多精彩内容。 什么是 Node.js &#xff1f; Node.js 是一个基于 Chrome V8 引擎的开源的跨平台的 JavaScript 运行时环境。 Node.js 采用了基于事件的、单线程的异步 I/O 架构。 Node.js 的组成部分 V8引擎 V8 引擎就是 JavaScrip…

使用 React 和 GPT-4 技术构建智能语言翻译应用

Midjourney 创作&#xff0c;Language Translation in future在今天的互联世界中&#xff0c;语言翻译在弥合沟通差距和促进全球合作方面发挥着至关重要的作用。随着像 OpenAI 的 GPT-4 这样先进的 AI 模型的出现&#xff0c;我们现在有机会创建高度精确和上下文感知的翻译工具…

USB键盘实现——带指示灯的键盘(九)

文章目录带指示灯的键盘set_report 类特殊请求实现类特殊请求USB 控制端点收到的数据增加一个输出端点实现配置描述符集合输出端点收到的数据带指示灯的键盘 要实现带指示灯的键盘&#xff0c;有两种方式 除控制端点和输入端点外&#xff0c;不额外增加端点&#xff0c;根据 …

不完全微分算法(SCL+ST代码)

PID控制器的基本算法,可以参看专栏的系列文章,链接如下: 三菱FX3U PLC 位置式PID算法(ST语言)_fx3u pid_RXXW_Dor的博客-CSDN博客三菱PLC自带的PID不必多说,大家可以自行查看指令说明。关于FX3U 增量式PID可以参看专栏的另一篇博客三菱PLC增量式PID算法FB(带死区设置和外部…

springCloud学习【3】之Docker完整版

文章目录一 初识Docker1.1 应用部署的环境问题1.2 Docker简介1.3 Docker解决操作系统环境差异1.4 Docker和虚拟机的区别1.5 Docker架构1.5.1 镜像和容器1.5.2 DockerHub1.5.3 Docker架构1.5.4 Docker工作流1.6 Docker的安装和启动1.7 安装步骤1.8 启动Docker1.9 配置镜像加速二…

总结802

早上&#xff1a; 6:23起床 6:43出门 7:00~7:40小湖读书 8:00~9:45机器人控制 9:50~11:30句句真研 11:31~12:10吃饭 12:12~12:22动漫 12:45~2:09午觉 2:00~4:15深度学习 4:26~4:39去图书馆 4:40~6:11高等数学第五讲 6:13~6:40跑步开合跳100胯下击掌100 6:50~7:20吃…

PS基础操作-抠图与导出-学习记录

目录 注&#xff1a;PS版本为2022&#xff0c;有个智能对象选择功能比较方便抠图 第一步&#xff1a;导入图像文件 第二步&#xff1a;基础的画布界面移动 鼠标滚轮上下滑动&#xff0c;可以是上下滚动界面 按住ctrl鼠标滚轮&#xff0c;可以左右滚动界面 按住alt鼠标滚轮…

华为OD机试题,用 Java 解【快递货车】问题 | 含解题说明

华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典本篇题目:快递货车 题目 一辆运送快递的…

【python刷题】LeetCode 2057E 值相等的最小索引(5种简单高效的解法)

作者&#xff1a;20岁爱吃必胜客&#xff08;坤制作人&#xff09;&#xff0c;近十年开发经验, 跨域学习者&#xff0c;目前于海外某世界知名高校就读计算机相关专业。荣誉&#xff1a;阿里云博客专家认证、腾讯开发者社区优质创作者&#xff0c;在CTF省赛校赛多次取得好成绩。…

问答ChatGPT-4:探索未来微数据中心IDC的发展趋势

从去年年底开始到现在&#xff0c;大众对以ChatGPT-4为主的人工智能AI的话题讨论盛况空前。这是一款由OpenAI发布的聊天机器人模型&#xff0c;一经上线&#xff0c;短短5天完成100万用户积累&#xff0c;并在最近实现月活用户破亿。实际上&#xff0c;ChatGPT和智能客服、智能…

CN RedGolf 集团利用 KEYPLUG 后门攻击 Windows 和 Linux 系统

被追踪为 RedGolf 的 CN 国家支持的威胁活动组织被归因于使用名为 KEYPLUG 的自定义 Windows 和 Linux 后门。 Recorded Future 告诉 The Hacker News&#xff1a;“RedGolf 是一个特别多产的CN国家支持的威胁组织&#xff0c;可能多年来一直活跃于全球范围内的广泛行业。” …