强引用、弱引用、软引用、虚引用
Java
执行 GC(垃圾回收)
判断对象是否存活有两种方式,分别是引用计数法和引用链法(可达性分析法)。
**引用计数:**Java堆中给每个对象都有一个引用计数器,每当某个对象在其它地方被引用时,该对象的计数器 +1;引用失效则 -1;
JDK 1.2
版本开始,对象的引用被划分为 4种级别,使程序能更加灵活地控制对象的生命周期。这 4
种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
强引用(StrongReference)
在一个线程内,无须引用直接可以使用的对象,强引用不会被JVM清理。我们平时申明变量使用的就是强引用,普通系统99%以上都是强引 用,比如,String s="Hello World"。
Java中的引用,有点像 C++的指针。通过引用,可以对堆中的对象进行操作。在Java 程序中,最常见的引用类型是强引用,它也是默认的引用类型。例如:StringBuffer str=new StringBuffer(“Hello World”);
强引用具备以下特点:
**(1)**强引用可以直接访问目标对象。
**(2)**强引用所指向的对象在任何时候都不会被系统回收。JVM宁愿抛出Out Of Memory异常也不会回收强引用所指向的对象。
**(3)**强引用可能导致内存泄漏。
通常来说,应用程序内部的内存泄露有两种情况。一种是虚拟机中存在程序无法使用的内存区域,另一种情况是程序中存在大量存活时间过长的对象。
(二) 软引用(SoftReference)
java中使用SoftRefence来表示软引用
软引用是除了强引用外最强的引用类型,我们可以通过java.lang.ref.SoftReference使用软引用。一个持有软引用的对象,它不会被JVM很快回收,JVM会根据当前堆的使用情况来判断何时回收。当堆使用率临近阙值时,才会去回收软引用的对象。只要有足够的内存,软引用便可能在内存中存活相当长一段时间。通过软引用,垃圾回收器就可以在内存不足时释放软引用可达的对象所占的内存空间。保证程序正常工作。
通过一个软引用申明,JVM抛出OOM之前,清理所有的软引用对象。垃圾回收器某个时刻决定回收软可达的对象的时候,会清理软引用,并可选的把引用存放到一个引用队列(ReferenceQueue)。
(三) 弱引用(WeakReference)
java中使用WeakReference来表示弱引用。如果某个对象与弱引用关联,那么当JVM在进行垃圾回收时,无论内存是否充足,都会回收此类对象。
通过一个弱引用申明。类似弱引用,只不过 Java 虚拟机会尽量让软引用的存活时间长一些,迫不得已才清理。
(四) 虚引用(PhantomReference)
java中使用PhantomReference来表示虚引用。
通过一个虚引用申明。仅用来处理资源的清理问题,比Object里面的finalize机制更灵活。get方法返回的永远是null,Java虚拟机不负责清理虚引用,但是它会把虚引用放到引用队列里面。
Java中 4种引用的级别和强度由高到低依次为:强引用 -> 软引用 -> 弱引用 -> 虚引用
当垃圾回收器回收时,某些对象会被回收,某些不会被回收。垃圾回收器会从根对象 Object来标记存活的对象,然后将某些不可达的对象和一些引用的对象进行回收。
ThreadLocal
1. 简介
ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。这里有几点需要注意:
- 因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。
- 既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题。
ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景
2. ThreadLocal与Synchronized的区别
ThreadLocal其实是与线程绑定的一个变量。ThreadLocal和Synchonized都用于解决多线程并发访问。
但是ThreadLocal与synchronized有本质的区别:
- Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
- Synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。
- 而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
2.1 一句话概括
一句话理解ThreadLocal,threadlocl是作为当前线程中属性ThreadLocalMap集合中的某一个Entry的key值Entry(threadlocl,value),虽然不同的线程之间threadlocal这个key值是一样,但是不同的线程所拥有的ThreadLocalMap是独一无二的,也就是不同的线程间同一个ThreadLocal(key)对应存储的值(value)不一样,从而到达了线程间变量隔离的目的,但是在同一个线程中这个value变量地址是一样的。
3. ThreadLocal 常见使用场景
如上文所述,ThreadLocal 适用于如下两种场景
- 每个线程需要有自己单独的实例
- 实例需要在多个方法中共享,但不希望被多线程共享
对于第一点,每个线程拥有自己实例,实现它的方式很多。例如可以在线程内部构建一个单独的实例。ThreadLocal 可以以非常方便的形式满足该需求。
对于第二点,可以在满足第一点(每个线程有自己的实例)的条件下,通过方法间引用传递的形式实现。ThreadLocal 使得代码耦合度更低,且实现更优雅。
4. 总结
前文我们讲过ThreadLocal的主要用途是实现线程间变量的隔离,表面上他们使用的是同一个ThreadLocal, 但是实际上使用的值value却是自己独有的一份。用一图直接表示threadlocal 的使用方式。
从图中我们可以当线程使用threadlocal 时,是将threadlocal当做当前线程thread的属性ThreadLocalMap 中的一个Entry的key值,实际上存放的变量是Entry的value值,我们实际要使用的值是value值。value值为什么不存在并发问题呢,因为它只有一个线程能访问,不存在资源竞争.
Entry将ThreadLocal作为Key,值作为value保存,它继承自WeakReference,注意构造函数里的第一行代码super(k),这意味着ThreadLocal对象是一个「弱引用」。可以看图1.
key是弱引用,而value是强引用,弱引用随着程序的运行会被垃圾回收掉,而强引用不会;
4.1 ThreadLocal使用注意点
-
避免读取到脏数据
现在代码中比较常见的使用到了线程池,线程池中的线程是可重复利用的,假设使用ThreadLocal 设置了value值,如果没有进行remove操作,下次该线程又被取到,通过get方法取到的值是上次设置的value值。
处理方式: 使用完后进行remove操作。
-
避免内存泄漏
使用了线程池后,由于线程一直存在, 对应的value值就不能被垃圾回收掉,容易产生内存泄漏问题。
由于ThreadLocalMap 的生命周期跟 Thread 一样长,对于重复利用的线程来说,如果没有手动删除(remove()方法)对应 key 就会导致entry(null,value)的对象越来越多,从而导致内存泄漏.
处理方式: 使用完后进行remove操作。
-
将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露。
cal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露。
4.2 具体使用场景
-
存储用户登录Token
-
场景二、数据库连接,处理数据库事务
-
场景三、数据跨层传递(controller,service, dao)
-
场景四、Spring使用ThreadLocal解决线程安全问题
我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全的“状态性对象”采用ThreadLocal进行封装,让它们也成为线程安全的“状态性对象”,因此有状态的Bean就能够以singleton的方式在多线程中正常工作了。