在kotlin—對象文件中,介紹了kotlin創(chuàng)建的對象的幾種方式,那么如何構建單例呢?
細心的朋友可能會發(fā)現簡單的對象聲明其實就是屬于餓漢式單例的實現方式,伴生對象看似是單例實則不是,除非使用@JvmStatic對其內部成員說明。
那么kotlin中怎么實現單例呢?
1、餓漢式
object StaticA {
val x = 123
}
等價于java中的:
public class StaticA {
public static final StaticA sInstance = new StaticA()
private StaticA () {
}
}
優(yōu)點:
- 實現簡單
- 線程安全,因為其在類加載時就進行了初始化,虛擬機內部保證其線程安全,保證對常量/靜態(tài)變量只進行一次初始化
缺點:
- 在類加載時就創(chuàng)建了靜態(tài)對象,實際上可能不會用到,所以對資源來說是浪費了,同時時類的初始化變慢,性能上并不是很好
2、懶漢式
懶漢式就是懶加載,在使用時在進行初始化,其實現如下:
class SingleA {
companion object {
val sInstance by lazy(LazyThreadSafetyMode.NONE) {
SingleA()
}
}
}
等價于java的:
public class SingleA {
private static sInstance;
private SingleA() {
}
public static SingleA getInstance() {
if (sInstance == null) {
sInstance = new SingleA()
}
return sInstance;
}
}
優(yōu)點:
- 延遲到使用時才進行初始化,提高了類加載的性能
缺點:
- 非線程安全,多個線程同時訪問情況下,會創(chuàng)建多個實例
3、懶漢同步方法式
此方法是在第2中方式上在給房間加鎖來實現,如下:
class SingleB {
companion object {
var sInstance: SingleB? = null
@Synchronized
fun getInstance(): SingleB? {
if (sInstance == null) {
sInstance = SingleB()
}
return sInstance
}
}
}
等價于java的:
public class SingleB {
private static SingleB sInstance;
private SingleB() {
}
public static synchronized SingleB getInstance() {
if (sInstance == null) {
sInstance = new SingleB();
}
return sInstance;
}
}
優(yōu)點:
- 延遲到使用時才進行初始化,提高了類加載的性能
- 對方法使用了同步鎖synchronized,保證了線程安全
缺點:
- synchronized應用在方法上,所有是對整個方法加了鎖,所以性能上稍差
4、懶漢同步塊式(推薦)
與第3中的方式差不多,不同的是同步鎖應用在方法的內部語句塊中:
class SingleC {
companion object {
val sIntance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
SingleC()
}
}
}
等價于java的:
public class SingleC {
private static SingleC sIntance;
private SingleC() {
}
public static SingleC getInstance() {
if (sIntance == null) {
synchronized (SingleC.class) {
if (sIntance == null) {
sIntance = new SingleC();
}
}
}
return sIntance;
}
}
優(yōu)點:
- 延遲到使用時才進行初始化,提高了類加載的性能
- 在創(chuàng)建對象的語句塊中使用了同步鎖synchronized,保證線程安全的同時,降低了鎖的作用范圍
缺點:
- 需要1-2次的空判斷
5、靜態(tài)內部類式(推薦)
靜態(tài)內部類的方式充分使用了語義的規(guī)則:
- 靜態(tài)的語義規(guī)則——》使用到時才進行初始化,實現了懶加載
- 靜態(tài)初始化是線程安全,線程的安全由虛擬機內部保證,保證靜態(tài)初始化時只被初始化一次
以下是kotlin靜態(tài)內部類的實現方式:
class SingleD {
companion object {
fun getInstance() = InstanceHelper.sSingle
}
object InstanceHelper {
val sSingle = SingleD()
}
}
等價于java的:
public class SingleD {
private SingleD() {
}
private static class InstanceHelper {
static SingleD sInstance = new SingleD();
}
public static SingleD getInstance() {
return InstanceHelper.sInstance;
}
}
優(yōu)點:
- 延遲到使用時才進行初始化,提高了類加載的性能
- 使用靜態(tài)初始化虛擬機保證線程安全的特性,實現了線程安全且鎖的性能在虛擬機內部實現性能較好
缺點:
- 需要多一個額外的靜態(tài)內部類來輔助實現
6、總結
綜合上述的單例實現就數懶漢同步塊式和靜態(tài)內部類式的性能較好,那如何選擇呢?
從其實現的不同方式不難發(fā)現:
- 懶漢同步塊式使用1-2兩次的判斷,所以執(zhí)行效率相比靜態(tài)內部類式較差,可以理解為空間優(yōu)先
- 靜態(tài)內部類需要使用一個輔助類來實現,所以空間效率上比懶漢同步塊式差,可以理解為空間換執(zhí)行效率,執(zhí)行效率優(yōu)先
那我們該如何抉擇呢?
如果空間上沒有要求而執(zhí)行效率上有要求,可以考慮使用靜態(tài)內部類方式;如果空間上有要求而執(zhí)行效率有要求,則考慮使用懶漢同步塊式;如果空間和執(zhí)行效率都有要求,需要權衡更需要那種方式而另一種則不得不做出犧牲,比較魚和熊掌不可兼得;如果空間和執(zhí)行效率都沒有要求,就看個人喜好了。