ThreadLocal之强、弱、软、虚引用(上)

简介: ThreadLocal之强、弱、软、虚引用

1.ThreadLocal简介


ThreadLocal提供线程局部变量。这些变量与正常的变量不同,因为每一个线程在访问ThreadLocal实例的时候(通过其get或set方法)都有自己的、独立初始化的变量副本。ThreadLocal实例通常是类中的私有静态字段,使用它的目的是希望将状态(例如,用户ID或事务ID)与线程关联起来。


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

1673416690176.jpg


2.永远的helloworld


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());
    }
}
/**
 * 需求1: 5个销售卖房子,集团高层只关心销售总量的准确统计数。
 * <p>
 * 需求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" + "共计卖出多少套: " + house.saleCount);
    }
}


小总结:


因为每个 Thread 内有自己的实例副本且该副本只由当前线程自己使用


既然其它 Thread 不可访问,那就不存在多线程间共享的问题。


统一设置初始值,但是每个线程对这个值的修改都是各自线程互相独立的


加入synchronized或者Lock控制资源的访问顺序

人手一份,大家各自安好,没必要抢夺


阿里ThreadLocal规范开始

1673416731745.jpg

class MyData
{
    ThreadLocal<Integer> threadLocalField = ThreadLocal.withInitial(() -> 0);
    public void add()
    {
        threadLocalField.set(1 + threadLocalField.get());
    }
}
/**
.【强制】必须回收自定义的 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();
        }
    }
}

1673416754414.jpg

1673416761707.jpg


3.Thread,ThreadLocal,ThreadLocalMap 关系


Thread和ThreadLocal

1673416883117.jpg

各自线程,人手一份


ThreadLocal和ThreadLocalMap

1673416891982.jpg

三者总概括

1673416899915.jpg

threadLocalMap实际上就是一个以threadLocal实例为key,任意对象为value的Entry对象(k,v键值对)。

1673416926113.jpg

当我们为threadLocal变量赋值,实际上就是以当前threadLocal实例为key,值为value的Entry往这个threadLocalMap中存放


近似的可以理解为:ThreadLocalMap从字面上就可以看出这是一个保存ThreadLocal对象的map(其实是以ThreadLocal为Key),不过是经过了两层包装的ThreadLocal对象

1673416914540.jpg

JVM内部维护了一个线程版的Map<Thread,T>(通过ThreadLocal对象的set方法,结果把ThreadLocal对象自己当做key,放进了ThreadLoalMap中),每个线程要用到这个T的时候,用当前的线程去Map里面获取,通过这样让每个线程都拥有了自己独立的变量,人手一份,竞争条件被彻底消除,在并发模式下是绝对安全的变量。


4.ThreadLocal内存泄露问题

1673416938167.jpg


1.什么是内存泄漏


不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露。


2.ThreadLocalMap与WeakReference


ThreadLocalMap 从字面上就可以看出这是一个保存ThreadLocal对象的map(其实是以它为Key),不过是经过了两层包装的ThreadLocal对象: (1)第一层包装是使用 WeakReference<ThreadLocal <?>> 将 ThreadLocal 对象变成一个弱引用的对象; (2)第二层包装是定义了一个专门的类 Entry 来扩展 WeakReference <ThreadLocal <?>>


1673416960592.jpg

3.强引用、软引用、弱引用、虚引用分别是什么?

关于引用的整体架构

1673416969232.jpg

一般new对象,就是Reference(强引用)

强引用又有三个子类:SoftReference(软),WeakReference(弱),PhantomReference(虚)


java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。

1673416981574.jpg

1.强引用(默认支持模式)


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


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


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

class MyObject
{
    //这个方法一般不用复写,我们只是为了教学给大家演示案例做说明
    @Override
    protected void finalize() throws Throwable
    {
        // finalize的通常目的是在对象被不可撤销地丢弃之前执行清理操作。
        System.out.println("-------invoke finalize method~!!!");
    }
}
public static void strongReference()
{
    MyObject myObject = new MyObject();
    System.out.println("-----gc before: "+myObject);
    myObject = null;
    System.gc();
    try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
    System.out.println("-----gc after: "+myObject);
}


2.软引用


软引用是一种相对强引用弱化了一些的引用,需要用java.lang.ref.SoftReference类来实现,可以让对象豁免一些垃圾收集。


对于只有软引用的对象来说,


当系统内存充足时它 不会 被回收,


当系统内存不足时它 会 被回收。


软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收!

private static void softReference()
    {
        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];//设置JVM启动内存小于20M,创建20MB对象
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            System.out.println("-----gc after内存不够: "+softReference.get());//输出:null,内存不够才会回收
        }
    }


1673416288187.jpg

3.弱引用


弱引用需要用java.lang.ref.WeakReference类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象占用的内存。

private static void weakReference()
    {
        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());//输出:null
    }


软引用和弱引用的适用场景


假如有一个应用需要读取大量的本地图片:


如果每次读取图片都从硬盘读取则会严重影响性能,


如果一次性全部加载到内存中又可能造成内存溢出。


此时使用软引用可以解决这个问题。


设计思路是:用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题。

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

软引用:内存充足 不回收,内存不充足 回收


弱引用:只要gc执行,内存不管是否充足,都会回收

相关文章
|
6月前
|
存储 监控 安全
解锁ThreadLocal的问题集:如何规避多线程中的坑
解锁ThreadLocal的问题集:如何规避多线程中的坑
279 0
|
6月前
|
算法 Java 程序员
Java垃圾回收机制详解
Java垃圾回收机制详解
|
4月前
|
存储 缓存 算法
(五)JVM成神路之对象内存布局、分配过程、从生至死历程、强弱软虚引用全面剖析
在上篇文章中曾详细谈到了JVM的内存区域,其中也曾提及了:Java程序运行过程中,绝大部分创建的对象都会被分配在堆空间内。而本篇文章则会站在对象实例的角度,阐述一个Java对象从生到死的历程、Java对象在内存中的布局以及对象引用类型。
123 8
|
算法 Java 程序员
JAVA垃圾回收机制
JAVA垃圾回收机制
61 0
JAVA垃圾回收机制
|
SQL 缓存 移动开发
Java内存泄漏知识(软引用、弱引用等)
要学习内存泄漏,我们要知道一些基础知识,如Java引用分类:
|
存储 算法 Java
Java垃圾回收机制深入理解
一、简介 Java垃圾回收机制是Java虚拟机(JVM)的核心组件之一,对于内存管理起到至关重要的作用。它能自动追踪并管理应用程序中创建的对象,当这些对象不再使用时,垃圾回收机制会自动回收其占用的内存,使这部分内存能够被再次利用。此机制极大地减少了开发者需要手动管理内存的负担,防止了因为疏忽导致的内存泄漏问题,是Java语言相较于C++等其他语言的一个显著优点。
310 0
|
存储 Java 数据库连接
深入理解Java ThreadLocal及其内存泄漏防范
一、ThreadLocal简介 在Java中,ThreadLocal是一种线程封闭的机制,其主要目的是为每个线程都创建一个单独的变量副本。这意味着,每个线程都可以独立地改变自己的副本,而不会影响其他线程的副本。 ThreadLocal常被用于解决多线程编程中的数据同步问题。例如,我们可以用ThreadLocal来保存数据库连接、Session等常见的线程不安全的变量。
628 0
|
存储 监控 安全
ThreadLocal之强、弱、软、虚引用(下)
ThreadLocal之强、弱、软、虚引用
ThreadLocal之强、弱、软、虚引用(下)
|
Java
强、软、弱、虚引用
强、软、弱、虚引用
90 0
|
Java 应用服务中间件 容器
ThreadLocal 内存泄露的实例分析
前言 之前写了一篇深入分析 ThreadLocal 内存泄漏问题是从理论上分析ThreadLocal的内存泄漏问题,这一篇文章我们来分析一下实际的内存泄漏案例。分析问题的过程比结果更重要,理论结合实际才能彻底分析出内存泄漏的原因。
3282 0