前言
筆者屬于算法小白一枚,本系列文章屬于算法的學(xué)習(xí)筆記恨旱,也希望能給算法小小白起到些許的指引作用座慰。如果有算法大佬不小心點(diǎn)了進(jìn)來,只能說一聲抱歉打擾了械荷。
思考
作為本系列文章的楔子共耍,我們今天不討論算法解題,而是來談?wù)勔粋€老朋友:單例模式吨瞎。相信大家對單例是非常熟悉的痹兜,但是如果要讓你手寫單例呢?寫起來磕磕碰碰還是嫻熟流暢颤诀?你會寫幾種字旭?分別有什么區(qū)別?現(xiàn)在大家都在往 kotlin 轉(zhuǎn)崖叫,那么在 kotlin 里面使用 object 關(guān)鍵字實(shí)現(xiàn)的單例又采用的是哪種實(shí)現(xiàn)方式呢遗淳?筆者從事 android 開發(fā)多年,在幾個互聯(lián)網(wǎng)大廠的面試中心傀,經(jīng)歷了兩次手寫單例的考驗(yàn)屈暗,所以在這里以手寫單例為楔子,為后面的手寫算法起一個拋磚引玉的作用脂男。
正文
什么是單例模式
在同一個進(jìn)程中养叛,有些時候我們需要某個類同時只保留一個對象,這個時候就應(yīng)該考慮單例模式的設(shè)計(jì)疆液。
單例模式的特點(diǎn)
- 單例模式只能有一個實(shí)例對象
- 單例模式必須創(chuàng)建自己的唯一實(shí)例
- 單例模式必須對外部提供這一實(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)變量就會進(jìn)行初始化戈擒。
- 靜態(tài)變量只會初始化一次,并且被所有的對象所共享艰毒。
懶漢式
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)建對象的時候進(jìn)行同步绊汹,而不需要同步多余的代碼稽屏。
- 第一重校驗(yàn)不為 null 的時候,直接返回對象西乖。否則準(zhǔn)備創(chuàng)建對象狐榔,此時需要進(jìn)行線程同步。
- 在創(chuàng)建對象之前還需要進(jìn)行第二重校驗(yàn)获雕,是為了確保等待同步的線程不會重復(fù)創(chuàng)建對象薄腻。
- 使用 volatile 關(guān)鍵字是為了禁止指令重排,確保對象初始化完全届案。
靜態(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)用的時候才會加載萝玷,滿足懶加載嫁乘。
- 靜態(tài)內(nèi)部類在加載的時候,jvm 會保證線程安全球碉。
總結(jié)
單例模式的實(shí)現(xiàn)很多蜓斧,我們這里只取精華,只講重點(diǎn)睁冬。最關(guān)鍵的是在面試過程中挎春,你是否可以在純文本編輯的環(huán)境下嫻熟的手寫以上幾種不同的單例,然后從容的告訴面試官它們之間的區(qū)別豆拨,最后再承受住面試官深挖的幾個為什么直奋。
借一句古語,與諸君共勉:紙上得來終覺淺施禾,絕知此事要躬行脚线。
一個小小的單例提醒了我們,在后面的算法學(xué)習(xí)中要腳踏實(shí)地的手寫算法解題弥搞,切記不可只做頭腦風(fēng)暴邮绿。在大廠的高級職位面試過程中渠旁,面試官最喜歡做的就是讓求職者手寫點(diǎn)代碼。不需要太多船逮,也不需要太難顾腊,就已經(jīng)可以看出很多東西了。兩位求職者挖胃,一個手寫起來嫻熟流暢杂靶,代碼強(qiáng)壯,格式工整酱鸭。另外一個先不說測試用例是否通過吗垮,寫的過程都磕磕碰碰。你是面試官凹髓,你會如何抉擇呢抱既?
最后附上 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;
}
}