單例模式概述
- 單例模式實(shí)現(xiàn)方式
- 為什么要使用單例模式
單例模式實(shí)現(xiàn)方式
餓漢式
類加載后就會(huì)將對(duì)象加載到內(nèi)存中佩捞,保證對(duì)象唯一
- 優(yōu)點(diǎn):由于是類加載后就創(chuàng)建到內(nèi)存改含,故不存在線程安全問題
- 缺點(diǎn):用或者不用蝶涩,都會(huì)直接創(chuàng)建對(duì)象,會(huì)導(dǎo)致浪費(fèi)內(nèi)存問題
// java實(shí)現(xiàn)
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
//kotlin實(shí)現(xiàn)
object Singleton{
}
懶漢式
懶漢模式在調(diào)用的時(shí)候才會(huì)創(chuàng)建對(duì)象,延遲創(chuàng)建蹲坷,多線程可能會(huì)創(chuàng)建多個(gè)實(shí)例對(duì)象
- 優(yōu)點(diǎn):調(diào)用的時(shí)候才創(chuàng)建,節(jié)省空間
- 缺點(diǎn):多線程下可能會(huì)創(chuàng)建多個(gè)實(shí)例對(duì)象
// java:
public class Singleton{
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
// kotlin:
class Singleton private constructor() {
companion object {
private var instance: Singleton? = null
get() {
if(field == null) {
field = Singleton()
}
return field
}
fun get() {
return instance!!
}
}
}
線程安全
線程安全是懶漢式的擴(kuò)展邑飒,并解決多線程下循签,仍然保持只創(chuàng)建單個(gè)實(shí)例,在創(chuàng)建對(duì)象的方法加synchronized關(guān)鍵字修飾疙咸,這樣就可以保證實(shí)例對(duì)象只會(huì)創(chuàng)建一個(gè)懦底。
- 優(yōu)點(diǎn):不存在多線程風(fēng)險(xiǎn),并且延遲創(chuàng)建
- 缺點(diǎn):在多線程下會(huì)導(dǎo)致bloc情況罕扎,如:有多個(gè)線程同時(shí)調(diào)用方法聚唐,其中一個(gè)搶占資源執(zhí)行創(chuàng)建實(shí)例對(duì)象,如果當(dāng)前創(chuàng)建比較耗時(shí)腔召,則其他線程會(huì)被bloc
// java:
public class Singleton{
private static Singleton instance = null;
private Singleton() {}
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
// kotlin:
class Singleton private constructor() {
companion object {
private var instance: Singleton? = null
get() {
if(field == null) {
field = Singleton()
}
return field
}
@Synchronized
fun get() {
return instance!!
}
}
}
雙重檢索
雙重檢索是線程安全方式的進(jìn)階版杆查,第一次判空是為了判斷當(dāng)前對(duì)象是否被創(chuàng)建,第二次判斷在synchronized后臀蛛,是為了判斷多個(gè)線程同時(shí)進(jìn)入的情況下亲桦,對(duì)象是否已經(jīng)創(chuàng)建崖蜜,這樣就保證了實(shí)例對(duì)象創(chuàng)建了一次。如:同時(shí)有兩個(gè)對(duì)象同時(shí)進(jìn)入bloc到synchronized這客峭,這個(gè)時(shí)候豫领,線程A搶到資源,就會(huì)進(jìn)入到synchronized的代碼塊內(nèi)舔琅,這個(gè)時(shí)候就會(huì)執(zhí)行創(chuàng)建實(shí)例對(duì)象等恐,完成之后線程B才會(huì)進(jìn)入,這個(gè)時(shí)候再判斷是否為空的時(shí)候就會(huì)為false备蚓,可以直接返回對(duì)象课蔬。
-
優(yōu)點(diǎn):
- 提升了效率,避免了線程安全時(shí)多個(gè)線程同時(shí)bloc在synchroized外面郊尝。
- 保證唯一二跋,同一時(shí)刻只有一個(gè)線程能創(chuàng)建對(duì)象
缺點(diǎn):代碼比較多(_)
-
雙重檢索會(huì)遇到空指針的問題,需要使用volatile關(guān)鍵字流昏,在多線程并發(fā)的情況出現(xiàn)java指令重排序的問題扎即,導(dǎo)致對(duì)象創(chuàng)建成功之后,未及時(shí)同步到主存中况凉,導(dǎo)致其他線程認(rèn)為主存對(duì)象還為null谚鄙,導(dǎo)致空指針。
- volatile關(guān)鍵字:保證可見性
// java:
public class Singleton{
private volatile static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if(instance == null) {
synchronized() {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
// kotlin:
// 無參數(shù)情況
class Singleton private constructor() {
companion object {
val INSTANCE: Singleton by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
Singleton()
}
}
}
// 有參數(shù)情況
class Singleton private constructor(v1: Int) {
companion object {
@Volatile
private var instance: Singleton? = null
fun getInstance(property: Int) =
instance ?: synchronized(this) {
instance ?: Singleton(property).also {
instance = it
}
}
}
}
靜態(tài)內(nèi)部類
靜態(tài)內(nèi)部類能保證線程唯一也是線程安全的茎刚,也能延遲加載襟锐,主要是類加載機(jī)制,當(dāng)類被加載的時(shí)候膛锭,內(nèi)部類并不會(huì)被加載粮坞,只有使用的時(shí)候才會(huì)創(chuàng)建實(shí)例加載到內(nèi)存中,所以能保證實(shí)例對(duì)象唯一初狰,也能延遲加載
- 優(yōu)點(diǎn): 保證唯一莫杈,延遲加載,代碼簡(jiǎn)潔
// java:
public class Singleton {
private Singleton() {}
private static class SingletonProvider {
private static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonProvider.instance;
}
}
// kotlin:
class Singleton private constructor(){
companion object {
val instance = SingletonProvider.holder
}
private object SingletonProvider {
val holder = Singleton()
}
}
為什么要使用單例模式
- 減少對(duì)象創(chuàng)建奢入,避免頻繁調(diào)用GC帶來的性能問題
- 用時(shí)創(chuàng)建筝闹,減少內(nèi)存消耗
- 多線程中數(shù)據(jù)不一致導(dǎo)致的問題
項(xiàng)目中常見的單例模式
- 使用網(wǎng)絡(luò)請(qǐng)求,如:OKHttp全局可以使用一個(gè)OKHttpClient實(shí)例對(duì)象