前言
筆者屬于算法小白一枚,本系列文章屬于算法的學(xué)習(xí)筆記,也希望能給算法小小白起到些許的指引作用。如果有算法大佬不小心點(diǎn)了進(jìn)來,只能說一聲抱歉打擾了。
思考
作為本系列文章的楔子,我們今天不討論算法解題,而是來談?wù)勔粋€(gè)老朋友:單例模式。相信大家對(duì)單例是非常熟悉的,但是如果要讓你手寫單例呢?寫起來磕磕碰碰還是嫻熟流暢?你會(huì)寫幾種?分別有什么區(qū)別?現(xiàn)在大家都在往 kotlin 轉(zhuǎn),那么在 kotlin 里面使用 object 關(guān)鍵字實(shí)現(xiàn)的單例又采用的是哪種實(shí)現(xiàn)方式呢?筆者從事 android 開發(fā)多年,在幾個(gè)互聯(lián)網(wǎng)大廠的面試中,經(jīng)歷了兩次手寫單例的考驗(yàn),所以在這里以手寫單例為楔子,為后面的手寫算法起一個(gè)拋磚引玉的作用。
正文
什么是單例模式
在同一個(gè)進(jìn)程中,有些時(shí)候我們需要某個(gè)類同時(shí)只保留一個(gè)對(duì)象,這個(gè)時(shí)候就應(yīng)該考慮單例模式的設(shè)計(jì)。
單例模式的特點(diǎn)
- 單例模式只能有一個(gè)實(shí)例對(duì)象
- 單例模式必須創(chuàng)建自己的唯一實(shí)例
- 單例模式必須對(duì)外部提供這一實(shí)例
單例模式的實(shí)現(xiàn)
餓漢式
public class Singleton {
private static Singleton INSTANCE = new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return INSTANCE;
}
}
優(yōu)點(diǎn):線程安全,使用沒有延遲。
缺點(diǎn):類加載即初始化實(shí)例,內(nèi)存浪費(fèi)。
面試官追問:為什么線程安全?
- 餓漢式本質(zhì)上是使用的是靜態(tài)變量。
- 在類加載的過程中,靜態(tài)變量就會(huì)進(jìn)行初始化。
- 靜態(tài)變量只會(huì)初始化一次,并且被所有的對(duì)象所共享。
懶漢式
public class Singleton {
private volatile static Singleton INSTANCE = null;
private Singleton() {
}
public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
優(yōu)點(diǎn):懶加載節(jié)省資源,線程安全。
缺點(diǎn):線程同步存在性能損耗
面試官追問:為什么采用雙重鎖檢驗(yàn)?為什么使用 volatile 關(guān)鍵字?
- 線程同步是存在性能損耗的,我們只需要在實(shí)際創(chuàng)建對(duì)象的時(shí)候進(jìn)行同步,而不需要同步多余的代碼。
- 第一重校驗(yàn)不為 null 的時(shí)候,直接返回對(duì)象。否則準(zhǔn)備創(chuàng)建對(duì)象,此時(shí)需要進(jìn)行線程同步。
- 在創(chuàng)建對(duì)象之前還需要進(jìn)行第二重校驗(yàn),是為了確保等待同步的線程不會(huì)重復(fù)創(chuàng)建對(duì)象。
- 使用 volatile 關(guān)鍵字是為了禁止指令重排,確保對(duì)象初始化完全。
靜態(tài)內(nèi)部類
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
private static Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
優(yōu)點(diǎn):懶加載節(jié)省資源,無性能損耗的線程安全
面試官追問:如何實(shí)現(xiàn)的懶加載?為什么線程安全
- 靜態(tài)內(nèi)部類只有被調(diào)用的時(shí)候才會(huì)加載,滿足懶加載。
- 靜態(tài)內(nèi)部類在加載的時(shí)候,jvm 會(huì)保證線程安全。
總結(jié)
單例模式的實(shí)現(xiàn)很多,我們這里只取精華,只講重點(diǎn)。最關(guān)鍵的是在面試過程中,你是否可以在純文本編輯的環(huán)境下嫻熟的手寫以上幾種不同的單例,然后從容的告訴面試官它們之間的區(qū)別,最后再承受住面試官深挖的幾個(gè)為什么。
借一句古語,與諸君共勉:紙上得來終覺淺,絕知此事要躬行。
一個(gè)小小的單例提醒了我們,在后面的算法學(xué)習(xí)中要腳踏實(shí)地的手寫算法解題,切記不可只做頭腦風(fēng)暴。在大廠的高級(jí)職位面試過程中,面試官最喜歡做的就是讓求職者手寫點(diǎn)代碼。不需要太多,也不需要太難,就已經(jīng)可以看出很多東西了。兩位求職者,一個(gè)手寫起來嫻熟流暢,代碼強(qiáng)壯,格式工整。另外一個(gè)先不說測(cè)試用例是否通過,寫的過程都磕磕碰碰。你是面試官,你會(huì)如何抉擇呢?
最后附上 kotlin 通過 object 關(guān)鍵字實(shí)現(xiàn)單例的字節(jié)碼反編譯出來的 java 源代碼,跟你想的是否一樣呢?
public final class Singleton {
public static final Singleton INSTANCE;
private Singleton() {
}
static {
Singleton var0 = new Singleton();
INSTANCE = var0;
}
}