字節(jié)跳動面試: 說說強(qiáng)引用、軟引用、弱引用、虛引用吧
本文公眾號來源:JavaKeeper作者:π大新本文已收錄至我的GitHub
我們都知道 JVM 垃圾回收中,GC判斷堆中的對象實(shí)例或數(shù)據(jù)是不是垃圾的方法有引用計(jì)數(shù)法和可達(dá)性算法兩種。
無論是通過引用計(jì)數(shù)算法判斷對象的引用數(shù)量,還是通過根搜索算法判斷對象的引用鏈?zhǔn)欠窨蛇_(dá),判定對象是否存活都與“引用”有關(guān)。
引用
先說說引用,Java中的引用,類似 C 語言中的指針。初學(xué) Java時(shí),我們就知道 Java 數(shù)據(jù)類型分兩大類,基本類型和引用類型。
基本類型:編程語言中內(nèi)置的最小粒度的數(shù)據(jù)類型。它包括四大類八種類型:
- 4種整數(shù)類型:byte、short、int、long
- 2種浮點(diǎn)數(shù)類型:float、double
- 1種字符類型:char
- 1種布爾類型:boolean
引用類型:引用類型指向一個對象,不是原始值,指向?qū)ο蟮淖兞渴且米兞俊T?Java 里,除了基本類型,其他類型都屬于引用類型,它主要包括:類、接口、數(shù)組、枚舉、注解
有了數(shù)據(jù)類型,JVM對程序數(shù)據(jù)的管理就規(guī)范化了,不同的數(shù)據(jù)類型,它的存儲形式和位置是不一樣的
怎么跑偏了,回歸正題,通過引用,可以對堆中的對象進(jìn)行操作。引用《Java編程思想》中的一段話,
”每種編程語言都有自己的數(shù)據(jù)處理方式。有些時(shí)候,程序員必須注意將要處理的數(shù)據(jù)是什么類型。你是直接操縱元素,還是用某種基于特殊語法的間接表示(例如C/C++里的指針)來操作對象。所有這些在 Java 里都得到了簡化,一切都被視為對象。因此,我們可采用一種統(tǒng)一的語法。盡管將一切都“看作”對象,但操縱的標(biāo)識符實(shí)際是指向一個對象的“引用”(reference)。”
比如:
Person person = new Person("張三");
這里的 person 就是指向Person 實(shí)例“張三”的引用,我們一般都是通過 person 來操作“張三”實(shí)例。
在 JDK 1.2 之前,Java 中的引用的定義很傳統(tǒng):如果 reference 類型的數(shù)據(jù)中存儲的數(shù)值代表的是另外一塊內(nèi)存的起始地址,就稱該 refrence 數(shù)據(jù)是代表某塊內(nèi)存、某個對象的引用。這種定義很純粹,但是太過狹隘,一個對象在這種定義下只有被引用或者沒有被引用兩種狀態(tài),對于如何描述一些“食之無味,棄之可惜”的對象就顯得無能為力。
比如我們希望能描述這樣一類對象:當(dāng)內(nèi)存空間還足夠時(shí),則能保留在內(nèi)存之中;如果內(nèi)存在進(jìn)行垃圾收集后還是非常緊張,則可以拋棄這些對象。很多系統(tǒng)的緩存功能都符合這樣的應(yīng)用場景。
在 JDK 1.2 之后,Java 對引用的概念進(jìn)行了擴(kuò)充,將引用分為
- 強(qiáng)引用(Strong Reference)
- 軟引用(Soft Reference)
- 弱引用(Weak Reference)
- 虛引用(Phantom Reference)
這四種引用強(qiáng)度依次逐漸減弱。
Java 中引入四種引用的目的是讓程序自己決定對象的生命周期,JVM 是通過垃圾回收器對這四種引用做不同的處理,來實(shí)現(xiàn)對象生命周期的改變。
JDK 8中的 UML關(guān)系圖

FinalReference 類是包內(nèi)可見,其他三種引用類型均為 public,可以在應(yīng)用程序中直接使用。
強(qiáng)引用
在 Java 中最常見的就是強(qiáng)引用,把一個對象賦給一個引用變量,這個引用變量就是一個強(qiáng)引用。類似 “Object obj = new Object()” 這類的引用。
當(dāng)一個對象被強(qiáng)引用變量引用時(shí),它處于可達(dá)狀態(tài),是不可能被垃圾回收器回收的,即使該對象永遠(yuǎn)不會被用到也不會被回收。
當(dāng)內(nèi)存不足,JVM 開始垃圾回收,對于強(qiáng)引用的對象,就算是出現(xiàn)了 OOM 也不會對該對象進(jìn)行回收,打死都不收。因此強(qiáng)引用有時(shí)也是造成 Java 內(nèi)存泄露的原因之一。
對于一個普通的對象,如果沒有其他的引用關(guān)系,只要超過了引用的作用域或者顯示地將相應(yīng)(強(qiáng))引用賦值為 null,一般認(rèn)為就是可以被垃圾收集器回收。(具體回收時(shí)機(jī)還要要看垃圾收集策略)。
coding~
public?class?StrongRefenenceDemo?{
????public?static?void?main(String[]?args)?{
????????Object?o1?=?new?Object();
????????Object?o2?=?o1;
????????o1?=?null;
????????System.gc();
????????System.out.println(o1);??//null
????????System.out.println(o2);??//java.lang.Object@2503dbd3
????}
}
demo 中盡管 o1已經(jīng)被回收,但是 o2 強(qiáng)引用 o1,一直存在,所以不會被GC回收
軟引用
軟引用是一種相對強(qiáng)引用弱化了一些的引用,需要用java.lang.ref.SoftReference 類來實(shí)現(xiàn),可以讓對象豁免一些垃圾收集。
軟引用用來描述一些還有用,但并非必需的對象。對于軟引用關(guān)聯(lián)著的對象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會把這些對象列進(jìn)回收范圍之中并進(jìn)行第二次回收。如果這次回收還是沒有足夠的內(nèi)存,才會拋出內(nèi)存溢出異常。
對于只有軟引用的對象來說:當(dāng)系統(tǒng)內(nèi)存充足時(shí)它不會被回收,當(dāng)系統(tǒng)內(nèi)存不足時(shí)它才會被回收。
coding~
//VM?options:?-Xms5m?-Xmx5m
public?class?SoftRefenenceDemo?{
????public?static?void?main(String[]?args)?{
????????softRefMemoryEnough();
????????System.out.println("------內(nèi)存不夠用的情況------");
????????softRefMemoryNotEnough();
????}
????private?static?void?softRefMemoryEnough()?{
????????Object?o1?=?new?Object();
????????SoftReferenceOutput
java.lang.Object@2503dbd3
java.lang.Object@2503dbd3
null
java.lang.Object@2503dbd3
------內(nèi)存不夠用的情況------
java.lang.Object@4b67cf4d
java.lang.Object@4b67cf4d
java.lang.OutOfMemoryError: Java heap space
at reference.SoftRefenenceDemo.softRefMemoryNotEnough(SoftRefenenceDemo.java:42)
at reference.SoftRefenenceDemo.main(SoftRefenenceDemo.java:15)
null
null
軟引用通常用在對內(nèi)存敏感的程序中,比如高速緩存就有用到軟引用,內(nèi)存夠用的時(shí)候就保留,不夠用就回收。
我們看下 Mybatis 緩存類 SoftCache 用到的軟引用
public?Object?getObject(Object?key)?{
????Object?result?=?null;
????SoftReference?softReference?=?(SoftReference)this.delegate.getObject(key);
????if?(softReference?!=?null)?{
????????result?=?softReference.get();
????????if?(result?==?null)?{
????????????this.delegate.removeObject(key);
????????}?else?{
????????????synchronized(this.hardLinksToAvoidGarbageCollection)?{
????????????????this.hardLinksToAvoidGarbageCollection.addFirst(result);
????????????????if?(this.hardLinksToAvoidGarbageCollection.size()?>?this.numberOfHardLinks)?{
????????????????????this.hardLinksToAvoidGarbageCollection.removeLast();
????????????????}
????????????}
????????}
????}
????return?result;
}
弱引用
弱引用也是用來描述非必需對象的,但是它的強(qiáng)度比軟引用更弱一些,被弱引用關(guān)聯(lián)的對象只能生存到下一次垃圾收集發(fā)生之前。當(dāng)垃圾收集器工作時(shí),無論當(dāng)前內(nèi)存是否足夠,都會回收掉只被弱引用關(guān)聯(lián)的對象。
弱引用需要用java.lang.ref.WeakReference類來實(shí)現(xiàn),它比軟引用的生存期更短。
對于只有弱引用的對象來說,只要垃圾回收機(jī)制一運(yùn)行,不管 JVM 的內(nèi)存空間是否足夠,都會回收該對象占用的內(nèi)存。
coding~
public?class?WeakReferenceDemo?{
????public?static?void?main(String[]?args)?{
????????Object?o1?=?new?Object();
????????WeakReference?w1?=?new?WeakReference(o1);
????????System.out.println(o1);
????????System.out.println(w1.get());
????????o1?=?null;
????????System.gc();
????????System.out.println(o1);
????????System.out.println(w1.get());
????}
}
Weak reference objects, which do not prevent their referents from being made finalizable, finalized, and then reclaimed. Weak references are most often used to implement canonicalizing mappings.
官方文檔這么寫的,弱引用常被用來實(shí)現(xiàn)規(guī)范化映射,JDK 中的 WeakHashMap 就是一個這樣的例子
面試官:既然你都知道弱引用,那能說說 WeakHashMap 嗎
public?class?WeakHashMapDemo?{
????public?static?void?main(String[]?args)?throws?InterruptedException?{
????????myHashMap();
????????myWeakHashMap();
????}
????public?static?void?myHashMap()?{
????????HashMap?map?=?new?HashMap();
????????String?key?=?new?String("k1");
????????String?value?=?"v1";
????????map.put(key,?value);
????????System.out.println(map);
????????key?=?null;
????????System.gc();
????????System.out.println(map);
????}
????public?static?void?myWeakHashMap()?throws?InterruptedException?{
????????WeakHashMap?map?=?new?WeakHashMap();
????????//String?key?=?"weak";
????????//?剛開始寫成了上邊的代碼
????????//思考一下,寫成上邊那樣會怎么樣??那可不是引用了
????????String?key?=?new?String("weak");
????????String?value?=?"map";
????????map.put(key,?value);
????????System.out.println(map);
????????//去掉強(qiáng)引用
????????key?=?null;
????????System.gc();
????????Thread.sleep(1000);
????????System.out.println(map);
????}
}
我們看下 ThreadLocal ?中用到的弱引用
static?class?ThreadLocalMap?{
????static?class?Entry?extends?WeakReference<ThreadLocal>>?{
????????Object?value;
????????Entry(ThreadLocal>?k,?Object?v)?{
????????????super(k);
????????????value?=?v;
????????}
????}
????//......
}
虛引用
虛引用也稱為“幽靈引用”或者“幻影引用”,它是最弱的一種引用關(guān)系。
虛引用,顧名思義,就是形同虛設(shè),與其他幾種引用都不太一樣,一個對象是否有虛引用的存在,完全不會對其生存時(shí)間構(gòu)成影響,也無法通過虛引用來取得一個對象實(shí)例。
虛引用需要java.lang.ref.PhantomReference 來實(shí)現(xiàn)。
如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時(shí)候都可能被垃圾回收器回收,它不能單獨(dú)使用也不能通過它訪問對象,虛引用必須和引用隊(duì)列(RefenenceQueue)聯(lián)合使用。
虛引用的主要作用是跟蹤對象垃圾回收的狀態(tài)。僅僅是提供了一種確保對象被 finalize 以后,做某些事情的機(jī)制。
PhantomReference 的 get 方法總是返回 null,因此無法訪問對應(yīng)的引用對象。其意義在于說明一個對象已經(jīng)進(jìn)入 finalization 階段,可以被 GC 回收,用來實(shí)現(xiàn)比 finalization 機(jī)制更靈活的回收操作。
換句話說,設(shè)置虛引用的唯一目的,就是在這個對象被回收器回收的時(shí)候收到一個系統(tǒng)通知或者后續(xù)添加進(jìn)一步的處理。
Java 允許使用 finalize() 方法在垃圾收集器將對象從內(nèi)存中清除出去之前做必要的清理工作。
public?class?PhantomReferenceDemo?{
????public?static?void?main(String[]?args)?throws?InterruptedException?{
????????Object?o1?=?new?Object();
????????ReferenceQueue?referenceQueue?=?new?ReferenceQueue();
????????PhantomReference?phantomReference?=?new?PhantomReference(o1,referenceQueue);
????????System.out.println(o1);
????????System.out.println(referenceQueue.poll());
????????System.out.println(phantomReference.get());
????????o1?=?null;
????????System.gc();
????????Thread.sleep(3000);
????????System.out.println(o1);
????????System.out.println(referenceQueue.poll());?//引用隊(duì)列中
????????System.out.println(phantomReference.get());
????}
}
java.lang.Object@4554617c
null
null
null
java.lang.ref.PhantomReference@74a14482
null
引用隊(duì)列
ReferenceQueue 是用來配合引用工作的,沒有ReferenceQueue 一樣可以運(yùn)行。
SoftReference、WeakReference、PhantomReference 都有一個可以傳遞 ReferenceQueue 的構(gòu)造器。
創(chuàng)建引用的時(shí)候,可以指定關(guān)聯(lián)的隊(duì)列,當(dāng) GC 釋放對象內(nèi)存的時(shí)候,會將引用加入到引用隊(duì)列。如果程序發(fā)現(xiàn)某個虛引用已經(jīng)被加入到引用隊(duì)列,那么就可以在所引用的對象的內(nèi)存被回收之前采取必要的行動,這相當(dāng)于是一種通知機(jī)制。
當(dāng)關(guān)聯(lián)的引用隊(duì)列中有數(shù)據(jù)的時(shí)候,意味著指向的堆內(nèi)存中的對象被回收。通過這種方式,JVM 允許我們在對象被銷毀后,做一些我們自己想做的事情。

最后,稍微了解下源碼中的實(shí)現(xiàn)
Reference源碼(JDK8)
強(qiáng)軟弱虛四種引用,我們有了個大概的認(rèn)識,我們也知道除了強(qiáng)引用沒有對應(yīng)的類型表示,是普遍存在的。剩下的三種引用都是 java.lang.ref.Reference 的直接子類。
那就會有疑問了,我們可以通過繼承 Reference,自定義引用類型嗎?
Abstract base class for reference objects. This class defines the operations common to all reference objects. Because reference objects are implemented in close cooperation with the garbage collector, this class may not be subclassed directly.
JDK 官方文檔是這么說的,Reference是所有引用對象的基類。這個類定義了所有引用對象的通用操作。因?yàn)橐脤ο笫桥c垃圾收集器緊密協(xié)作而實(shí)現(xiàn)的,所以這個類可能不能直接子類化。
Reference 的4種狀態(tài)
- Active:新創(chuàng)建的引用實(shí)例處于Active狀態(tài),但當(dāng)GC檢測到該實(shí)例引用的實(shí)際對象的可達(dá)性發(fā)生某些改變(實(shí)際對象處于 GC roots 不可達(dá))后,它的狀態(tài)將變化為
Pending或者Inactive。如果 Reference 注冊了ReferenceQueue,則會切換為Pending,并且Reference會加入pending-Reference鏈表中,如果沒有注冊ReferenceQueue,會切換為Inactive。 - Pending:當(dāng)引用實(shí)例被放置在pending-Reference 鏈表中時(shí),它處于Pending狀態(tài)。此時(shí),該實(shí)例在等待一個叫Reference-handler的線程將此實(shí)例進(jìn)行enqueue操作。如果某個引用實(shí)例沒有注冊在一個引用隊(duì)列中,該實(shí)例將永遠(yuǎn)不會進(jìn)入Pending狀態(tài)
- Enqueued:在ReferenceQueue隊(duì)列中的Reference的狀態(tài),如果Reference從隊(duì)列中移除,會進(jìn)入
Inactive狀態(tài) - Inactive:一旦某個引用實(shí)例處于Inactive狀態(tài),它的狀態(tài)將不再會發(fā)生改變,同時(shí)說明該引用實(shí)例所指向的實(shí)際對象一定會被GC所回收

Reference的構(gòu)造函數(shù)和成員變量
public?abstract?class?Reference<T>?{
???//引用指向的對象
???private?T?referent;????
???//?reference被回收后,當(dāng)前Reference實(shí)例會被添加到這個隊(duì)列中
???volatile?ReferenceQueue?super?T>?queue;
???//下一個Reference實(shí)例的引用,Reference實(shí)例通過此構(gòu)造單向的鏈表
???volatile?Reference?next;
???//由transient修飾,基于狀態(tài)表示不同鏈表中的下一個待處理的對象,主要是pending-reference列表的下一個元素,通過JVM直接調(diào)用賦值
???private?transient?Reference?discovered;
???//?等待加入隊(duì)列的引用列表,這里明明是個Reference類型的對象,官方文檔確說是個list?
???//因?yàn)镚C檢測到某個引用實(shí)例指向的實(shí)際對象不可達(dá)后,會將該pending指向該引用實(shí)例,
???//discovered字段則是用來表示下一個需要被處理的實(shí)例,因此我們只要不斷地在處理完當(dāng)前pending之后,將discovered指向的實(shí)例賦予給pending即可。所以這個pending就相當(dāng)于是一個鏈表。
???private?static?Reference?pending?=?null;
????
????/*?--?Constructors?--?*/
????Reference(T?referent)?{
????????this(referent,?null);
????}
????Reference(T?referent,?ReferenceQueue?super?T>?queue)?{
????????this.referent?=?referent;
????????this.queue?=?(queue?==?null)???ReferenceQueue.NULL?:?queue;
????}
}
Reference 提供了兩個構(gòu)造器,一個帶引用隊(duì)列 ReferenceQueue,一個不帶。
帶 ReferenceQueue 的意義在于我們可以從外部通過對 ReferenceQueue 的操作來了解到引用實(shí)例所指向的實(shí)際對象是否被回收了,同時(shí)我們也可以通過 ReferenceQueue ?對引用實(shí)例進(jìn)行一些額外的操作;但如果我們的引用實(shí)例在創(chuàng)建時(shí)沒有指定一個引用隊(duì)列,那我們要想知道實(shí)際對象是否被回收,就只能夠不停地輪詢引用實(shí)例的get() 方法是否為空了。
值得注意的是虛引用 PhantomReference,由于它的 get() 方法永遠(yuǎn)返回 null,因此它的構(gòu)造函數(shù)必須指定一個引用隊(duì)列。
這兩種查詢實(shí)際對象是否被回收的方法都有應(yīng)用,如 WeakHashMap 中就選擇去查詢 queue 的數(shù)據(jù),來判定是否有對象將被回收;而 ThreadLocalMap,則采用判斷 get() 是否為 null 來作處理。
實(shí)例方法(和ReferenceHandler線程不相關(guān)的方法)
private?static?Lock?lock?=?new?Lock();
//?獲取持有的referent實(shí)例
public?T?get()?{
????return?this.referent;
}
//?把持有的referent實(shí)例置為null
public?void?clear()?{
????this.referent?=?null;
}
//?判斷是否處于enqeued狀態(tài)
public?boolean?isEnqueued()?{
????return?(this.queue?==?ReferenceQueue.ENQUEUED);
}
//?入隊(duì)參數(shù),同時(shí)會把referent置為null
public?boolean?enqueue()?{
????return?this.queue.enqueue(this);
}
ReferenceHandler線程
通過上文的討論,我們知道一個Reference實(shí)例化后狀態(tài)為Active,其引用的對象被回收后,垃圾回收器將其加入到pending-Reference鏈表,等待加入ReferenceQueue。
ReferenceHandler線程是由Reference靜態(tài)代碼塊中建立并且運(yùn)行的線程,它的運(yùn)行方法中依賴了比較多的本地(native)方法,ReferenceHandler線程的主要功能就pending list中的引用實(shí)例添加到引用隊(duì)列中,并將pending指向下一個引用實(shí)例。
//?控制垃圾回收器操作與Pending狀態(tài)的Reference入隊(duì)操作不沖突執(zhí)行的全局鎖
//?垃圾回收器開始一輪垃圾回收前要獲取此鎖
//?所以所有占用這個鎖的代碼必須盡快完成,不能生成新對象,也不能調(diào)用用戶代碼
static?private?class?Lock?{?}
private?static?Lock?lock?=?new?Lock();
private?static?class?ReferenceHandler?extends?Thread?{
????private?static?void?ensureClassInitialized(Class>?clazz)?{
????????try?{
????????????Class.forName(clazz.getName(),?true,?clazz.getClassLoader());
????????}?catch?(ClassNotFoundException?e)?{
????????????throw?(Error)?new?NoClassDefFoundError(e.getMessage()).initCause(e);
????????}
????}
????static?{
????????ensureClassInitialized(InterruptedException.class);
????????ensureClassInitialized(Cleaner.class);
????}
????ReferenceHandler(ThreadGroup?g,?String?name)?{
????????super(g,?name);
????}
????public?void?run()?{
????????while?(true)?{
????????????tryHandlePending(true);
????????}
????}
}
static?boolean?tryHandlePending(boolean?waitForNotify)?{
????Reference?r;
????Cleaner?c;
????try?{
????????synchronized?(lock)?{
????????????//?判斷pending-Reference鏈表是否有數(shù)據(jù)
????????????if?(pending?!=?null)?{
????????????????//?如果有Pending?Reference,從列表中取出
????????????????r?=?pending;
????????????????c?=?r?instanceof?Cleaner???(Cleaner)?r?:?null;
????????????????//?unlink?'r'?from?'pending'?chain
????????????????pending?=?r.discovered;
????????????????r.discovered?=?null;
????????????}?else?{
????//?如果沒有Pending?Reference,調(diào)用wait等待
????????????????if?(waitForNotify)?{
????????????????????lock.wait();
????????????????}
????????????????//?retry?if?waited
????????????????return?waitForNotify;
????????????}
????????}
????}?catch?(OutOfMemoryError?x)?{
????????Thread.yield();
????????return?true;
????}?catch?(InterruptedException?x)?{
????????return?true;
????}
????//?Fast?path?for?cleaners
????if?(c?!=?null)?{
????????c.clean();
????????return?true;
????}
????ReferenceQueue?super?Object>?q?=?r.queue;
????if?(q?!=?ReferenceQueue.NULL)?q.enqueue(r);
????return?true;
}
//ReferenceHandler線程是在Reference的static塊中啟動的
static?{
????//?ThreadGroup繼承當(dāng)前執(zhí)行線程(一般是主線程)的線程組
????ThreadGroup?tg?=?Thread.currentThread().getThreadGroup();
????for?(ThreadGroup?tgn?=?tg;
?????????tgn?!=?null;
?????????tg?=?tgn,?tgn?=?tg.getParent());
????//?創(chuàng)建線程實(shí)例,命名為Reference?Handler,配置最高優(yōu)先級和后臺運(yùn)行(守護(hù)線程),然后啟動
????Thread?handler?=?new?ReferenceHandler(tg,?"Reference?Handler");
????//?ReferenceHandler線程有最高優(yōu)先級
????handler.setPriority(Thread.MAX_PRIORITY);
????handler.setDaemon(true);
????handler.start();
????//?provide?access?in?SharedSecrets
????SharedSecrets.setJavaLangRefAccess(new?JavaLangRefAccess()?{
????????@Override
????????public?boolean?tryHandlePendingReference()?{
????????????return?tryHandlePending(false);
????????}
????});
}
由于ReferenceHandler線程是Reference的靜態(tài)代碼創(chuàng)建的,所以只要Reference這個父類被初始化,該線程就會創(chuàng)建和運(yùn)行,由于它是守護(hù)線程,除非 JVM 進(jìn)程終結(jié),否則它會一直在后臺運(yùn)行(注意它的run()方法里面使用了死循環(huán))。
ReferenceQueue源碼
public?class?ReferenceQueue<T>?{
????public?ReferenceQueue()?{?}
?//?內(nèi)部類Null類繼承自ReferenceQueue,覆蓋了enqueue方法返回false
????private?static?class?Null<S>?extends?ReferenceQueue<S>?{
????????boolean?enqueue(Reference?extends?S>?r)?{
????????????return?false;
????????}
????}
??//?用于標(biāo)識沒有注冊Queue
????static?ReferenceQueue?NULL?=?new?Null<>();
????//?用于標(biāo)識已經(jīng)處于對應(yīng)的Queue中
????static?ReferenceQueue?ENQUEUED?=?new?Null<>();
????//?靜態(tài)內(nèi)部類,作為鎖對象
????static?private?class?Lock?{?};
????/*?互斥鎖,用于同步ReferenceHandler的enqueue和用戶線程操作的remove和poll出隊(duì)操作?*/
????private?Lock?lock?=?new?Lock();
????//?引用鏈表的頭節(jié)點(diǎn)
????private?volatile?Reference?extends?T>?head?=?null;
????//?引用隊(duì)列長度,入隊(duì)則增加1,出隊(duì)則減少1
????private?long?queueLength?=?0;
????//?入隊(duì)操作,只會被Reference實(shí)例調(diào)用
????boolean?enqueue(Reference?extends?T>?r)?{?/*?Called?only?by?Reference?class?*/
????????synchronized?(lock)?{
???//?如果引用實(shí)例持有的隊(duì)列為ReferenceQueue.NULL或者ReferenceQueue.ENQUEUED則入隊(duì)失敗返回false
????????????ReferenceQueue>?queue?=?r.queue;
????????????if?((queue?==?NULL)?||?(queue?==?ENQUEUED))?{
????????????????return?false;
????????????}
????????????assert?queue?==?this;
????????????//?當(dāng)前引用實(shí)例已經(jīng)入隊(duì),那么它本身持有的引用隊(duì)列實(shí)例置為ReferenceQueue.ENQUEUED
????????????r.queue?=?ENQUEUED;
????????????//?如果鏈表沒有元素,則此引用實(shí)例直接作為頭節(jié)點(diǎn),否則把前一個引用實(shí)例作為下一個節(jié)點(diǎn)
????????????r.next?=?(head?==?null)???r?:?head;
????????????//?當(dāng)前實(shí)例更新為頭節(jié)點(diǎn),也就是每一個新入隊(duì)的引用實(shí)例都是作為頭節(jié)點(diǎn),已有的引用實(shí)例會作為后繼節(jié)點(diǎn)
????????????head?=?r;
????????????//?隊(duì)列長度增加1
????????????queueLength++;
????????????//?特殊處理FinalReference,VM進(jìn)行計(jì)數(shù)
????????????if?(r?instanceof?FinalReference)?{
????????????????sun.misc.VM.addFinalRefCount(1);
????????????}
????????????//?喚醒所有等待的線程
????????????lock.notifyAll();
????????????return?true;
????????}
????}
????//?引用隊(duì)列的poll操作,此方法必須在加鎖情況下調(diào)用
????private?Reference?extends?T>?reallyPoll()?{???????/*?Must?hold?lock?*/
????????Reference?extends?T>?r?=?head;
????????if?(r?!=?null)?{
????????????@SuppressWarnings("unchecked")
????????????Reference?extends?T>?rn?=?r.next;
????????????//?更新next節(jié)點(diǎn)為頭節(jié)點(diǎn),如果next節(jié)點(diǎn)為自身,說明已經(jīng)走過一次出隊(duì),則返回null
????????????head?=?(rn?==?r)???null?:?rn;
????????????r.queue?=?NULL;
????????????//?當(dāng)前頭節(jié)點(diǎn)變更為環(huán)狀隊(duì)列,考慮到FinalReference尚為inactive和避免重復(fù)出隊(duì)的問題
????????????r.next?=?r;
????????????//?隊(duì)列長度減少1
????????????queueLength--;
????????????if?(r?instanceof?FinalReference)?{
????????????????sun.misc.VM.addFinalRefCount(-1);
????????????}
????????????return?r;
????????}
????????return?null;
????}
????//?隊(duì)列的公有poll操作,主要是加鎖后調(diào)用reallyPoll
????public?Reference?extends?T>?poll()?{
????????if?(head?==?null)
????????????return?null;
????????synchronized?(lock)?{
????????????return?reallyPoll();
????????}
????}
//?移除引用隊(duì)列中的下一個引用元素,實(shí)際上也是依賴于reallyPoll的Object提供的阻塞機(jī)制
????public?Reference?extends?T>?remove(long?timeout)
????????throws?IllegalArgumentException,?InterruptedException
????{
????????if?(timeout?0)?{
????????????throw?new?IllegalArgumentException("Negative?timeout?value");
????????}
????????synchronized?(lock)?{
????????????Reference?extends?T>?r?=?reallyPoll();
????????????if?(r?!=?null)?return?r;
????????????long?start?=?(timeout?==?0)???0?:?System.nanoTime();
????????????for?(;;)?{
????????????????lock.wait(timeout);
????????????????r?=?reallyPoll();
????????????????if?(r?!=?null)?return?r;
????????????????if?(timeout?!=?0)?{
????????????????????long?end?=?System.nanoTime();
????????????????????timeout?-=?(end?-?start)?/?1000_000;
????????????????????if?(timeout?<=?0)?return?null;
????????????????????start?=?end;
????????????????}
????????????}
????????}
????}
????public?Reference?extends?T>?remove()?throws?InterruptedException?{
????????return?remove(0);
????}
????void?forEach(Consumer?super?Reference?extends?T>>?action)?{
????????for?(Reference?extends?T>?r?=?head;?r?!=?null;)?{
????????????action.accept(r);
????????????@SuppressWarnings("unchecked")
????????????Reference?extends?T>?rn?=?r.next;
????????????if?(rn?==?r)?{
????????????????if?(r.queue?==?ENQUEUED)?{
????????????????????//?still?enqueued?->?we?reached?end?of?chain
????????????????????r?=?null;
????????????????}?else?{
????????????????????//?already?dequeued:?r.queue?==?NULL;?->
????????????????????//?restart?from?head?when?overtaken?by?queue?poller(s)
????????????????????r?=?head;
????????????????}
????????????}?else?{
????????????????//?next?in?chain
????????????????r?=?rn;
????????????}
????????}
????}
}
ReferenceQueue只存儲了Reference鏈表的頭節(jié)點(diǎn),真正的Reference鏈表的所有節(jié)點(diǎn)是存儲在Reference實(shí)例本身,通過屬性 next 拼接的,ReferenceQueue提供了對Reference鏈表的入隊(duì)、poll、remove等操作
各類知識點(diǎn)總結(jié)
下面的文章都有對應(yīng)的原創(chuàng)精美PDF,在持續(xù)更新中,可以來找我催更~
- 92頁的Mybatis
- 129頁的多線程
- 141頁的Servlet
- 158頁的JSP
- 76頁的集合
- 64頁的JDBC
- 105頁的數(shù)據(jù)結(jié)構(gòu)和算法
- 142頁的Spring
- 58頁的過濾器和監(jiān)聽器
- 30頁的HTTP
- 42頁的SpringMVC
- Hibernate
- AJAX
- Redis
- ......
掃碼或者微信搜Java3y?免費(fèi)領(lǐng)取原創(chuàng)思維導(dǎo)圖、精美PDF。在公眾號回復(fù)「888」領(lǐng)取,PDF內(nèi)容純手打有任何不懂歡迎來問我。
原創(chuàng)電子書
原創(chuàng)思維導(dǎo)圖

![]() |
|


