基礎篇:JAVA引用類型和ThreadLocal


前言

平時並發編程,除了維護修改共享變量的場景,有時我們也需要為每一個線程設置一個私有的變量,進行線程隔離,java提供的ThreadLocal可以幫助我們實現,而講到ThreadLocal則不得不講講java的四種引用,不同的引用類型在GC時表現是不一樣的,引用類型Reference有助於我們了解如何快速回收某些對象的內存或對實例的GC控制

  • 四種引用類型在JVM的生命周期
  • 引用隊列(ReferenceQueue)
  • ThreadLocal的實現原理和使用
  • FinalReference和finalize方法的實現原理
  • Cheaner機制

關注公眾號,一起交流,微信搜一搜: 潛行前行

1 四種引用類型在JVM的生命周期

強引用(StrongReference)

  • 創建一個對象並賦給一個引用變量,強引用有引用變量指向時,永遠也不會垃圾回收,JVM寧願拋出OutOfMemory異常也不會回收該對象;強引用對象的創建,如
Integer index = new Integer(1); String name = "csc"; 復制代碼
  • 如果中斷所有引用變量和強引用對象的聯系(將引用變量賦值為null),JVM則會在合適的時間就會回收該對象

軟引用(SoftReference)

  • 和強用引用不同點在於內存不足時,該類型引用對象會被垃圾處理器回收
  • 使用軟引用能防止內存泄露,增強程序的健壯性。SoftReference的特點是它的一個實例保存對一個Java對象的軟引用,該軟引用的存在不妨礙垃圾收集線程對該Java對象的回收
  • SoftReference類所提供的get()方法返回Java對象的強引用。另外,一旦垃圾線程回收該對象之后,get()方法將返回null
    String name = "csc"; //軟引用的創建 SoftReference<String> softRef = new SoftReference<String>(name); System.out.println(softRef.get()); 復制代碼

弱引用(WeakReference)

  • 特點:無論內存是否充足,只要進行GC,都會被回收

    static class User{ String name; public User(String name){ this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } } //弱引用的創建 WeakReference<User> softRef = new WeakReference<User>(new User("csc")); System.out.println(softRef.get().getName()); //輸出 csc System.gc(); System.out.println(softRef.get()); //輸出 null //弱引用Map WeakHashMap<String, String> map = new WeakHashMap<String, String>(); 復制代碼

虛引用(PhantomReference)

  • 特點:如同虛設,和沒有引用沒什么區別;虛引用和軟引用、弱引用不同,它並不決定對象的生命周期。如果一個對象與虛引用關聯,則跟沒有引用與之關聯一樣,在任何時候都可能被垃圾回收器回收
  • 要注意的是,虛引用必須和引用隊列關聯使用,當垃圾回收器准備回收一個對象時,如果發現它還有虛引用,就會把這個虛引用加入到與之關聯的引用隊列中。程序可以通過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否將要被垃圾回收
public static void main(String[] args) { ReferenceQueue<User> queue = new ReferenceQueue<>(); PhantomReference<User> pr = new PhantomReference<User>(new User("csc"), queue); //PhantomRefrence的get方法總是返回null,因此無法訪問對應的引用對象。 System.out.println(pr.get()); // null System.gc(); System.out.println(queue.poll()); //獲取被垃圾回收的"xb"的引用ReferenceQueue } 復制代碼
引用類型 被垃圾回收時間 場景 生存時間
強引用 從來不會 對象的一般狀態 JVM停止運行時終止
軟引用 當內存不足時 對象緩存 內存不足時終止
弱引用 正常垃圾回收時 對象緩存 垃圾回收后終止
虛引用 正常垃圾回收時 跟蹤對象的垃圾回收 垃圾回收后終止

2 引用隊列(ReferenceQueue)

  • 引用隊列可以配合軟引用、弱引用及虛引用使用;當引用的對象將要被JVM回收時,會將其加入到引用隊列中
  ReferenceQueue<String> queue = new ReferenceQueue<String>(); WeakReference<String> pr = new WeakReference<String>("wxj", queue); System.gc(); System.out.println(queue.poll().get()); // 獲取即將被回收的字符串 wxj 復制代碼

3 ThreadLocal的原理和使用

ThreadLocal 的實現原理

  • 每個線程都內置了一個ThreadLocalMap對象
public class Thread implements Runnable { /* 當前線程對於的ThreadLocalMap實例,ThreadLocal<T>作為Key, * T對應的對象作為value */ ThreadLocal.ThreadLocalMap threadLocals = null; 復制代碼
  • ThreadLocalMap作為ThreadLocal的內部類,實現了類似HashMap的功能,它元素Entry繼承於WeakReference,key值是ThreadLocal,value是引用變量。也就是說jvm發生GC時value對象則會被回收
public class ThreadLocal<T> { //ThreadLocal對象對應的hash值,使用一個靜態AtomicInteger實現 private final int threadLocalHashCode = nextHashCode(); //設置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); } //獲取value public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) {     //獲取當前線程的ThreadLocalMap,再使用對象ThreadLocal獲取對應的value ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } .... //類似HashMap的類 static class ThreadLocalMap { //使用開放地址法解決hash沖突 //如果hash出的index已經有值,通過算法在后面的若干位置尋找空位 private Entry[] table; ... //Entry 是弱引用 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只是為每個線程保存一個副本變量,副本變量的修改不影響其他線程的變量值,因此ThreadLocal不能實現共享變量的安全性

ThreadLocal 使用場景

  • 線程安全,包裹線程不安全的工具類,比如java.text.SimpleDateFormat類,當然jdk1.8已經給出了對應的線程安全的類java.time.format.DateTimeFormatter
  • 線程隔離,比如數據庫連接管理、Session管理、mdc日志追蹤等。

ThreadLocal內存泄露和WeakReference

  • ThreadLocalMap.Entry是弱引用,弱引用對象是不管有沒有被引用都會被垃圾回收
  • 發生內存泄漏一般是在線程池的線程,生命周期長,threadLocals引用會一直存在,當其存放的ThreadLocal被回收(弱引用生命周期短)后,它對應的Entity成了e.get()==null的實例。線程不死則Entity一直不會被回收,這就發生了內存泄漏
  • 如果線程跨業務操作相同的ThreadLocal,還會造成變量安全問題
  • 通常在使用完ThreadLocal最好調用它的remove();在ThreadLocal的get、set的時候,最好檢查當前Entity的key是否為null,如果是null就把Entity釋放掉,value則會被垃圾回收

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM