淘寶客可道cms網(wǎng)站建設(shè)重慶seo整站優(yōu)化方案范文
文章目錄
- 單例模式的結(jié)構(gòu)
- 如何控制只有一個對象呢
- 怎么設(shè)計這個類的內(nèi)部對象
- 外部怎么訪問
- 單例模式的主要有以下角色
- 單例模式的實現(xiàn)
- 餓漢式 1:靜態(tài)變量
- 餓漢式 2:靜態(tài)代碼塊
- 懶漢式 1:線程不安全
- 懶漢式 2:線程安全—方法級上鎖
- 懶漢式 3:雙重檢查鎖🚀
- 懶漢式 4:靜態(tài)內(nèi)部類🚀
- 餓漢式 3:枚舉🚀
- 存在的問題
- 序列化破壞單例
- 反射破壞單例
- JDK 源碼 - Runtime 類
單例模式(Singleton Pattern)是 Java 中最簡單的設(shè)計模式之一。這種類型的設(shè)計模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式。
這種模式涉及到一個單一的類,該類負責創(chuàng)建自己的對象,同時確保只有單個對象被創(chuàng)建。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,不需要實例化該類的對象。
- 所謂的單例模式保證某個類在程序中有且只有一個對象,類比現(xiàn)實生活中的地球類,只有一個地球?qū)ο?/li>
單例模式的結(jié)構(gòu)
如何控制只有一個對象呢
- 我們創(chuàng)建一個對象是通過該類的構(gòu)造方法進行創(chuàng)建對象
- 我們不能讓外部進行創(chuàng)建對象,不然誰都能創(chuàng)建對象,那么就保證不了單例,那么我們的構(gòu)造方法必須是private修飾,來控制對象的創(chuàng)建,也就是私有的構(gòu)造方法
- 外部不能創(chuàng)建對象,那么這個創(chuàng)建唯一的對象的任務(wù)也就會在我們單例類來進行實現(xiàn)
怎么設(shè)計這個類的內(nèi)部對象
- 首先我們知道在外部是創(chuàng)建不了對象的,所以這個對象的類型肯定不能是成員的,必須是靜態(tài)的,通過類調(diào)用
- 因為成員變量的屬性是通過我們的對象進行賦值,我們只有一個對象,如果是成員變量,就先要有對象才能賦值,而賦值了才有那個唯一的對象,就出現(xiàn)了死循環(huán)
外部怎么訪問
- 一般屬性我們用private進行修飾,為了實現(xiàn)其封裝性
- 直接通過公開的方法獲取這個唯一的對象
單例模式的主要有以下角色
- 單例類。只能創(chuàng)建一個實例的類
- 訪問類。使用單例類
單例模式的實現(xiàn)
單例設(shè)計模式分類兩種:
- 餓漢式:類加載就會導(dǎo)致該單實例對象被創(chuàng)建
- 懶漢式:類加載不會導(dǎo)致該單實例對象被創(chuàng)建,而是首次使用該對象時才會創(chuàng)建
餓漢式 1:靜態(tài)變量
public class Singleton {//1定義靜態(tài)成員變量,在類加載時創(chuàng)建private static Singleton singleton=new Singleton();//2私有化構(gòu)造方法private Singleton(){}// 3.提供一個公共的訪問方式,讓外界獲取該對象public static Singleton getSingleton() {return singleton;}
}
- 因為singleton是類成員變量,在我 Singleton類加載過程中的初始化過程中進行創(chuàng)建對象賦值,因為只會執(zhí)行一次,所以是天然的線程安全
- 缺點:singleton 對象是隨著類的加載而創(chuàng)建的,如果該對象很大,卻一直沒有使用就會造成內(nèi)存的浪費
餓漢式 2:靜態(tài)代碼塊
該方式在成員位置聲明 Singleton 類型的靜態(tài)變量,而對象的創(chuàng)建是在靜態(tài)代碼塊中,也是隨著類的加載而創(chuàng)建。
public class Singleton {//1定義靜態(tài)成員變量,在類加載時創(chuàng)建private static Singleton singleton;static {singleton=new Singleton();}//2私有化構(gòu)造方法private Singleton(){}// 3.提供一個公共的訪問方式,讓外界獲取該對象public static Singleton getSingleton() {return singleton;}
}
該方式和餓漢式的方式 1 基本一樣,所以該方式也存在內(nèi)存浪費問題。
懶漢式 1:線程不安全
該方式在成員位置聲明 Singleton 類型的靜態(tài)變量,并沒有進行對象的賦值操作,那么什么時候賦值的呢?
當調(diào)用 getInstance()
方法獲取 Singleton 類的對象的時候才創(chuàng)建 Singleton 類的對象,這樣就實現(xiàn)了懶加載效果。
public class Singleton {//1定義靜態(tài)成員變量,在類加載時創(chuàng)建private static Singleton singleton;//2私有化構(gòu)造方法private Singleton(){}// 3.提供一個公共的訪問方式,讓外界獲取該對象public static Singleton getSingleton(){if(singleton==null){singleton=new Singleton();}return singleton;}
}
測試
public class Client {public static void main(String[] args) {Thread thread1=new Thread(()->{System.out.println(Singleton.getSingleton());});thread1.start();System.out.println(Singleton.getSingleton());}
}
//com.lsc.itheima.pattern.singleton.demo3.Singleton@3d075dc0
//com.lsc.itheima.pattern.singleton.demo3.Singleton@6b047ec
- 發(fā)現(xiàn)不是同一個對象,沒有實現(xiàn)其單例的效果
- 為什么不是線程安全的
- 比如我們的thread1線程調(diào)用了getSingleton,進行if(singleton==null)判斷,進入了判斷體
- 然后我們的main線程搶占了CPU,進行運行,進行if(singleton==null)判斷,因為thread1線程并沒有進行創(chuàng)建對象,也進入了判斷體
- 故兩個線程各自創(chuàng)建了對應(yīng)的對象
懶漢式 2:線程安全—方法級上鎖
在懶漢式 1 的基礎(chǔ)上,使用 synchronized
關(guān)鍵字對getSingleton這個方法進行加鎖
public class Singleton {//1定義靜態(tài)成員變量,在類加載時創(chuàng)建private static Singleton singleton;//2私有化構(gòu)造方法private Singleton(){}// 3.提供一個公共的訪問方式,讓外界獲取該對象public static synchronized Singleton getSingleton(){if(singleton==null){singleton=new Singleton();}return singleton;}
}
- 但是該方法的執(zhí)行效率特別低。
- 懶漢模式中加鎖的問題,對于 getSingleton 方法來說,絕大部分的操作都是讀操作,讀操作是線程安全的,所以我們沒必讓每個線程必須持有鎖才能調(diào)用該方法,我們需要調(diào)整加鎖的時機。由此也產(chǎn)生了一種新的實現(xiàn)模式:雙重檢查鎖模式
- 注意:其實只有在初始化singleton的時候才會出現(xiàn)線程安全問題,一旦初始化完成就不存在了。
懶漢式 3:雙重檢查鎖🚀
public class Singleton {//1定義靜態(tài)成員變量,在類加載時創(chuàng)建private static Singleton singleton;//2私有化構(gòu)造方法private Singleton(){}// 3.提供一個公共的訪問方式,讓外界獲取該對象public static Singleton getSingleton(){//第一次判斷,如果singleton不為null,不進入搶鎖階段,直接返回實例if(singleton==null){synchronized (Singleton.class) {// 第二次判斷,搶占到鎖以后再次判斷if (singleton == null) {singleton = new Singleton();}}}return singleton;}
}
雙重檢查鎖模式是一種非常好的單例實現(xiàn)模式,解決了單例、性能、線程安全問題,上面的雙重檢測鎖模式看上去完美無缺,其實是存在問題,在多線程的情況下,可能會出現(xiàn)空指針問題,出現(xiàn)問題的原因是JVM在實例化對象的時候會進行優(yōu)化和指令重排序操作。
- 要解決雙重檢查鎖模式帶來空指針異常的問題,只需要使用 volatile 關(guān)鍵字, volatile 關(guān)鍵字可以保證可見性和有序性。
- 使用volatile關(guān)鍵字保證單例對象初始化不會被中斷,保證其他線程獲得的對象一定是初始化完成的對象
- 比如現(xiàn)在t1線程執(zhí)行到了初始化的實例對象,正在執(zhí)行new操作,還沒完全結(jié)束(但是LazySingleTon!=null),然后t2線程執(zhí)行到了判斷唯一的對象是否為空,對于t2發(fā)現(xiàn)不為空,就直接返回了,但是返回的對象是沒有完全初始化的,所以需要加volatile,就相當加了一層內(nèi)存屏障,保證其他線程返回的對象必須等操作完全結(jié)束才能執(zhí)行return語句
public class Singleton {//1定義靜態(tài)成員變量,在類加載時創(chuàng)建// 聲明Singleton類型的變量,使用volatile保證可見性和有序性private static volatile Singleton singleton;//2私有化構(gòu)造方法private Singleton(){}// 3.提供一個公共的訪問方式,讓外界獲取該對象public static Singleton getSingleton(){// 第一次判斷,如果instance不為null,不需要搶占鎖,直接返回對象if(singleton==null){synchronized (Singleton.class) {// 第二次判斷,搶占到鎖以后再次判斷if (singleton == null) {singleton = new Singleton();}}}return singleton;}
}
懶漢式 4:靜態(tài)內(nèi)部類🚀
靜態(tài)內(nèi)部類單例模式中實例由內(nèi)部類創(chuàng)建,由于 JVM 在加載外部類的過程中,是不會加載靜態(tài)內(nèi)部類的,只有內(nèi)部類的屬性/方法被調(diào)用時才會被加載,并初始化其靜態(tài)屬性。靜態(tài)屬性由于被 static 修飾,保證只被實例化一次,并且嚴格保證實例化順序。
public class Singleton {//2私有化構(gòu)造方法private Singleton(){}// 定義一個靜態(tài)內(nèi)部類private static class SingletonHolder {// 在內(nèi)部類中聲明并初始化外部類的對象private static final Singleton singleton = new Singleton();}// 提供公共的訪問方式public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}
第一次加載 Singleton 類時不會去初始化 singleton,只有第一次調(diào)用 getInstance(),虛擬機加載 SingletonHolder 類并初始化 singleton,這樣不僅能確保線程安全,也能保證 Singleton 類的唯一性。
靜態(tài)內(nèi)部類單例模式是一種優(yōu)秀的單例模式,是開源項目中比較常用的一種單例模式。在沒有加任何鎖的情況下,保證了多線程下的安全,并且沒有任何性能影響和空間的浪費。
餓漢式 3:枚舉🚀
首先,枚舉方式是餓漢式單例模式,如果不考慮浪費內(nèi)存空間的問題,這是極力推薦的單例實現(xiàn)模式。
因為枚舉類型是線程安全的,并且只會裝載一次,設(shè)計者充分的利用了枚舉的這個特性來實現(xiàn)單例模式。
枚舉的寫法非常簡單,而且枚舉方式是所用單例實現(xiàn)中唯一一種不會被破壞的單例實現(xiàn)模式。
public class SingleTon {/*** 餓漢式:枚舉實現(xiàn)*/public enum Singleton {INSTANCE}
}
存在的問題
有兩種方式可以使上面定義的單例類可以創(chuàng)建多個對象(枚舉方式除外),分別是序列化和反射。
枚舉方式是利用了 Java 特性實現(xiàn)的單例模式,不會被破壞,其他實現(xiàn)方式都有可能會被破壞
序列化破壞單例
問題演示:下面代碼運行結(jié)果是false
,表明序列化和反序列化破壞了單例設(shè)計模式。
public class Client {public static void main(String[] args) throws IOException, ClassNotFoundException {writeObject2File();Singleton s1 = readObjectFormFile();Singleton s2 = readObjectFormFile();
// System.out.println(s1 == s2); // false}//從文件讀取數(shù)據(jù)public static Singleton readObjectFormFile() throws IOException, ClassNotFoundException {//創(chuàng)建對象輸入流對象ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\a.txt"));//2.讀取對象Singleton singleton =(Singleton) objectInputStream.readObject();//3釋放資源objectInputStream.close();return singleton;}//向文件寫數(shù)據(jù)public static void writeObject2File() throws IOException {//1獲取Singleton對象Singleton singleton = Singleton.getSingleton();//創(chuàng)建對象輸出流對象ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\a.txt"));// 3.寫對象objectOutputStream.writeObject(singleton);// 4.釋放資源objectOutputStream.close();}
}
解決方案:在 Singleton 類中添加readResolve()
方法。
在反序列化時被反射調(diào)用,如果定義了這個方法,就返回這個方法的值,如果沒有定義,則返回新 new 出來的對象。
/*** 雙重加鎖方式(解決序列化破解單例模式)*/
public class Singleton implements Serializable {//1定義靜態(tài)成員變量,在類加載時創(chuàng)建 聲明Singleton類型的變量,使用volatile保證可見性和有序性private static volatile Singleton singleton;//2私有化構(gòu)造方法private Singleton(){}// 3.提供一個公共的訪問方式,讓外界獲取該對象public static Singleton getSingleton(){// 第一次判斷,如果instance不為null,不需要搶占鎖,直接返回對象if(singleton==null){synchronized (Singleton.class) {// 第二次判斷,搶占到鎖以后再次判斷if (singleton == null) {singleton = new Singleton();}}}return singleton;}/*** 下面是為了解決序列化反序列化破解單例模式* 當進行反序列化時,會自動調(diào)用該方法,將該方法的返回值直接返回*/private Object readResolve() {return getSingleton();}}
反射破壞單例
public class Client {public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {//1 獲取Singleton的字節(jié)碼對象Class<Singleton> singletonClass = Singleton.class;//2獲取無參構(gòu)造方法對象Constructor<Singleton> declaredConstructor = singletonClass.getDeclaredConstructor();//3取消訪問檢查declaredConstructor.setAccessible(true);// 4.創(chuàng)建Singleton對象Singleton s1 = (Singleton) declaredConstructor.newInstance();Singleton s2 = (Singleton) declaredConstructor.newInstance();System.out.println(s1 == s2); // false}
}
解決方案:當通過反射方式調(diào)用構(gòu)造方法進行創(chuàng)建時,直接拋異常
public class Singleton {//1定義靜態(tài)成員變量,在類加載時創(chuàng)建 聲明Singleton類型的變量,使用volatile保證可見性和有序性private static volatile Singleton singleton;private static boolean flag = false;//2私有化構(gòu)造方法// 私有構(gòu)造方法private Singleton() {synchronized (Singleton.class) {// 如果是true,說明非第一次訪問,直接拋一個異常,如果是false,說明第一次訪問if (flag) {throw new RuntimeException("不能創(chuàng)建多個對象");}flag = true;}}// 3.提供一個公共的訪問方式,讓外界獲取該對象public static Singleton getSingleton(){// 第一次判斷,如果instance不為null,不需要搶占鎖,直接返回對象if(singleton==null){synchronized (Singleton.class) {// 第二次判斷,搶占到鎖以后再次判斷if (singleton == null) {singleton = new Singleton();}}}return singleton;}
}
JDK 源碼 - Runtime 類
Runtime 類使用的就是單例設(shè)計模式。
源碼查看:下面是 Runtime 類的源碼,是靜態(tài)變量方式的餓漢單例模式。
public class Runtime {private static Runtime currentRuntime = new Runtime();/*** Returns the runtime object associated with the current Java application.* Most of the methods of class <code>Runtime</code> are instance* methods and must be invoked with respect to the current runtime object.** @return the <code>Runtime</code> object associated with the current* Java application.*/public static Runtime getRuntime() {return currentRuntime;}/** Don't let anyone else instantiate this class */private Runtime() {}...
}
/*** RuntimeDemo*/
public class RuntimeDemo {public static void main(String[] args) throws IOException {// 獲取Runtime類的對象Runtime runtime = Runtime.getRuntime();// 調(diào)用runtime的方法exec,參數(shù)要的是一個命令Process process = runtime.exec("ifconfig");// 調(diào)用process對象的獲取輸入流的方法InputStream is = process.getInputStream();byte[] arr = new byte[1024 * 1024 * 100];// 讀取數(shù)據(jù)int len = is.read(arr); // 返回讀到的字節(jié)的個數(shù)// 將字節(jié)數(shù)組轉(zhuǎn)換為字符串輸出到控制臺System.out.println(new String(arr, 0, len, "GBK"));}
}