單例模式—讀書筆記
單例模式,顧名思義,就是用來創建一個獨一無二的,只能有一個實例的對象的入場券。因爲在實際應用中,有些對象我們只需要一個,比如說,線程池、緩存、對話框、註冊表等。
當然,我們也可以使用全局對象來達到和單例模式一樣的作用,但是全局對象有一些缺點,比如說:如果將對象賦值給一個全局變量,那麼必須在程序一開始就創建好該對象,如果這個對象很耗資源,但是我們又沒有用到它,那就會形成一種浪費。或許我們還可以使用其他方法來達到單例的相同的目的,但是,單例模式很簡單,而且經得住時間考驗,是值得學習的。
單例模式有很多種實現,下面我們來看一下各種實現的優缺點。
方法1
SingleTon.java
public class SingleTon {
private static SingleTon singleTon;//私有的,不能在類外部訪問,只能在類的內部訪問
private SingleTon(){
}
public static SingleTon getInstance(){
if (singleTon == null){
singleTon = new SingleTon();
}
return singleTon;
}
}
SingleTon.kt
class SingleTon private constructor(){
companion object {
val instance by lazy { SingleTon() }//用到的時候初始化,且只初始化一次
}
}
總結
單例模式可以保證在任何時刻都只有一個對象(私有構造器)。一般情況下,常常用來管理共享的資源,例如數據庫連接和線程池。
方法2:線程安全
上面的java代碼在多線程的時候,會出現安全問題。下面我們處理一下多線程的情況:
public class SingleTon{
private static SingleTon singleTon;
private SingleTon(){
}
private static synchronized SingleTon getInstance(){
if (singleTon == null){
singleTon = new SingleTon();
}
return singleTon;
}
}
SingleTon.kt
class SingleTon private constructor(){
companion object {
private var INSTANCE: SingleTon? = null
//在Kotlin中加鎖
@Synchronized
fun getInstance(): SingleTon {
if (INSTANCE == null) {
INSTANCE = SingleTon()
}
return INSTANCE!!
}
}
}
在上面的方法中,我們在getInstance()方法中,添加了synchoronized關鍵字,可以保證同時只有一個線程進入該方法,不會有兩個線程可以同時進入這方法。
但是,這個方法有一些缺點,只有第一次執行此方法的時候,才需要同步,換句話說,一旦設置了singleTon對象,就不再需要同步這個方法了。不然的話,性能會很低。
改進
1、如果getInstance()方法的性能對應用程序不是很關鍵,那就什麼都別做。
2、使用“餓漢”創建實例,而不是延遲實例化的做法
public class SingleTon{
private static SingleTon singleTon = new SingleTon();//jvm保證線程安全,且在加載這個類的時候,馬上創建此唯一的單例實例。
private SingleTon(){
}
public static SingleTon getInstance(){
return singleTon;
}
}
SingleTon.kt
object SingleTon{
}
3、用“雙重檢查加鎖”,在getInstance()中減少使用同步
使用雙重加鎖,首先檢查是否實例已經創建了,如果尚未創建,“才”進行同步,這樣一來,只有第一次需要同步,這正是我們想要的。
public class SingleTon{
private volatile static SingleTon singleTon;
private SingleTon(){
}
public static SingleTon getInstance(){
if (singleTon == null){
synchronized (SingleTon.class){
if (singleTon == null){
singleTon = new SingleTon();
}
}
}
return singleTon;
}
}
volatile 關鍵字確保,當singleTon變量被初始化成SingleTon實例時,多個線程正確的處理singleTon變量。
SingleTon.kt
class SingleTon private constructor() {
companion object {
val singleton by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
SingleTon()
}
}
}
方法3:靜態內部類
SingleTon.java
public class SingleTon{
private SingleTon(){
}
private static class SingleTonHolder{
private static SingleTon singleTon = new SingleTon();
}
private static SingleTon getInstance(){
return SingleTonHolder.singleTon;
}
}
靜態內部類的優點是:外部類加載時,不需要立即加載內部類,內部類不加載則不去初始化singleTon,因而不佔用內存。只有當getInstance()方法第一次被調用時,纔會去初始化singleTon,第一個調用getInstance方法會導致虛擬機加載SingleTonHolder這個類,這種方法不僅能確保線程安全,也能保證單例的唯一性,而且這做到了延遲單例的實例化。
在singleTon創建的過程中,是怎麼保證線程安全的呢?如果多個線程同時初始化一個類,那麼虛擬機會保證只會有一個線程去初始化這個類,其他線程都需要阻塞等待,直到初始化完畢。因此靜態內部類的形式的單例可以保證線程安全,也能保證單例的唯一性,同時也延遲了單例的實例化。
SingleTon.kt
class SingleTon private constructor(){
companion object {//靜態方法
val instance = Holder.holder
}
private object Holder {//私有靜態內部類
val holder= SingleTon()
}
}