一、概述
1.1簡(jiǎn)介
單例模式(Singleton Pattern)是 Java 中最簡(jiǎn)單的設(shè)計(jì)模式之一。這種類型的設(shè)計(jì)模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對(duì)象的最佳方式。
這種模式涉及到一個(gè)單一的類,該類負(fù)責(zé)創(chuàng)建自己的對(duì)象,同時(shí)確保只有單個(gè)對(duì)象被創(chuàng)建。這個(gè)類提供了一種訪問(wèn)其唯一的對(duì)象的方式,可以直接訪問(wèn),不需要實(shí)例化該類的對(duì)象。
1.2結(jié)構(gòu)
單例模式的主要有以下角色:
- 單例類。只能創(chuàng)建一個(gè)實(shí)例的類
- 訪問(wèn)類。使用單例類
1.3單例模式的實(shí)現(xiàn)
單例設(shè)計(jì)模式分類兩種:
餓漢式:類加載就會(huì)導(dǎo)致該單實(shí)例對(duì)象被創(chuàng)建
懶漢式:類加載不會(huì)導(dǎo)致該單實(shí)例對(duì)象被創(chuàng)建,而是首次使用該對(duì)象時(shí)才會(huì)創(chuàng)建
餓漢式-方式1(靜態(tài)變量方式)?
package com.yanyu.Singleton;
public class Singleton {
//私有構(gòu)造方法
private Singleton() {}
//在成員位置創(chuàng)建該類的對(duì)象
private static Singleton instance = new Singleton();
//對(duì)外提供靜態(tài)方法獲取該對(duì)象
public static Singleton getInstance() {
return instance;
}
}
這是一個(gè)單例模式的實(shí)現(xiàn),確保在程序運(yùn)行中只有一個(gè)該類的實(shí)例對(duì)象存在。
具體實(shí)現(xiàn):
1. 將類的構(gòu)造方法私有化,防止外部直接通過(guò)構(gòu)造方法創(chuàng)建對(duì)象。
2. 在類的成員位置創(chuàng)建一個(gè)私有靜態(tài)的對(duì)象 instance,確保了Singleton類不能在外部被實(shí)例化,只能在類內(nèi)部創(chuàng)建對(duì)象。
3. 對(duì)外提供一個(gè)靜態(tài)的方法 getInstance(),返回該類的對(duì)象 instance,確保在程序中只有一個(gè)該類的實(shí)例對(duì)象存在。
4. 由于 instance 是私有的靜態(tài)成員,在類加載時(shí)就已經(jīng)創(chuàng)建了該對(duì)象,所以在 getInstance() 方法中直接返回 instance 即可。由于instance是static成員,類的所有對(duì)象共享同一份instance,從而保證了在應(yīng)用中只有一個(gè)Singleton對(duì)象被創(chuàng)建。
需要注意的是,該實(shí)現(xiàn)方法并未考慮線程安全性,可能會(huì)存在線程安全問(wèn)題
使用單例模式可以避免重復(fù)創(chuàng)建對(duì)象,節(jié)省內(nèi)存空間,并且可以確保對(duì)象在程序中只有一個(gè)實(shí)例,保證數(shù)據(jù)一致性。
package com.yanyu.Singleton;
public class client{
public static void main(String[]args){
//創(chuàng)建singletion類的對(duì)象
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
//判斷獲取到的兩個(gè)是否是同一個(gè)對(duì)象
System.out.println(instance == instance1);
}
}
?說(shuō)明:
該方式在成員位置聲明Singleton類型的靜態(tài)變量,并創(chuàng)建Singleton類的對(duì)象instance。instance對(duì)象是隨著類的加載而創(chuàng)建的。如果該對(duì)象足夠大的話,而一直沒(méi)有使用就會(huì)造成內(nèi)存的浪費(fèi)。
餓漢式-方式2(靜態(tài)代碼塊方式)
package com.yanyu.Singleton;
public class Singleton {
//私有構(gòu)造方法
private Singleton() {}
//在成員位置創(chuàng)建該類的對(duì)象
private static Singleton instance;
static {
instance = new Singleton();
}
//對(duì)外提供靜態(tài)方法獲取該對(duì)象
public static Singleton getInstance() {
return instance;
}
}
?這是一種餓漢式單例模式的實(shí)現(xiàn)方式,通過(guò)靜態(tài)代碼塊來(lái)初始化單例對(duì)象,保證了線程安全性和唯一性。在類被加載時(shí)就已經(jīng)創(chuàng)建了單例對(duì)象,因此也叫做餓漢式單例模式。
因?yàn)轭惣虞d是線程安全的,不需要考慮多線程的情況。
但是,由于對(duì)象是在類被加載時(shí)就創(chuàng)建的,因此可能會(huì)造成資源浪費(fèi)。如果該單例對(duì)象在程序運(yùn)行期間一直沒(méi)有被使用,那么一直占據(jù)著一部分內(nèi)存空間,會(huì)對(duì)系統(tǒng)的性能產(chǎn)生一定的影響。
另外,由于單例對(duì)象是靜態(tài)的,所以對(duì)于某些需要?jiǎng)討B(tài)實(shí)現(xiàn)的場(chǎng)景,該實(shí)現(xiàn)方式并不合適。
懶漢式-方式1(線程不安全)
package com.yanyu.Singleton;
public class Singleton {
//私有構(gòu)造方法
private Singleton() {}
//在成員位置創(chuàng)建該類的對(duì)象
private static Singleton instance;
//對(duì)外提供靜態(tài)方法獲取該對(duì)象
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
?具體實(shí)現(xiàn)是通過(guò)私有構(gòu)造方法來(lái)禁止類的外部創(chuàng)建對(duì)象,然后在類的成員位置創(chuàng)建該類的唯一對(duì)象instance,并通過(guò)靜態(tài)方法getInstance()來(lái)返回該對(duì)象。在getInstance()方法中,先判斷instance是否為null,如果是則創(chuàng)建該對(duì)象,否則直接返回該對(duì)象。
這種實(shí)現(xiàn)方式稱為懶漢式單例模式,因?yàn)橹挥性诘谝淮握{(diào)用getInstance()方法時(shí)才會(huì)創(chuàng)建對(duì)象,而之后的調(diào)用都會(huì)直接返回已創(chuàng)建的對(duì)象。從而避免了多個(gè)實(shí)例導(dǎo)致的資源浪費(fèi)和數(shù)據(jù)不一致等問(wèn)題。
?懶漢式-方式2(線程安全)
package com.yanyu.Singleton;
public class Singleton {
//私有構(gòu)造方法
private Singleton() {}
//在成員位置創(chuàng)建該類的對(duì)象
private static Singleton instance;
//對(duì)外提供靜態(tài)方法獲取該對(duì)象
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
?懶漢式相對(duì)于餓漢式來(lái)說(shuō),它是延遲加載的,只有在真正需要使用該對(duì)象時(shí)才會(huì)進(jìn)行初始化,這樣可以節(jié)省資源和減少初始化時(shí)間。但是,懶漢式實(shí)現(xiàn)需要考慮線程安全問(wèn)題,因?yàn)樵诙嗑€程環(huán)境下,如果多個(gè)線程同時(shí)調(diào)用 getInstance() 方法,可能會(huì)創(chuàng)建多個(gè)實(shí)例,導(dǎo)致單例模式失效。因此,需要使用 synchronized 關(guān)鍵字在方法內(nèi)部進(jìn)行同步,保證線程安全。
但是在getInstance()方法上添加了synchronized關(guān)鍵字,導(dǎo)致該方法的執(zhí)行效果特別低。
懶漢式-方式3(雙重檢查鎖)
對(duì)于?
getInstance()
?方法來(lái)說(shuō),絕大部分的操作都是讀操作,讀操作是線程安全的,所以我們沒(méi)必讓每個(gè)線程必須持有鎖才能調(diào)用該方法,我們需要調(diào)整加鎖的時(shí)機(jī)。由此也產(chǎn)生了一種新的實(shí)現(xiàn)模式:雙重檢查鎖模式?
package com.yanyu.Singleton;
public class Singleton {
//私有構(gòu)造方法
private Singleton() {}
private static Singleton instance;
//對(duì)外提供靜態(tài)方法獲取該對(duì)象
public static Singleton getInstance() {
//第一次判斷,如果instance不為null,不進(jìn)入搶鎖階段,直接返回實(shí)際
if(instance == null) {
synchronized (Singleton.class) {
//搶到鎖之后再次判斷是否為空
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
是怕多線程創(chuàng)建多個(gè)對(duì)象,如果不為空直接返回提高效率,為空就要加鎖防止
首先進(jìn)行一次非空判斷,如果instance為null,才進(jìn)行同步代碼塊的搶鎖,再次判斷instance是否為空,確保只有一個(gè)線程可以創(chuàng)建對(duì)象。
雙重檢查鎖模式是一種非常好的單例實(shí)現(xiàn)模式,解決了單例、性能、線程安全問(wèn)題,上面的雙重檢測(cè)鎖模式看上去完美無(wú)缺,其實(shí)是存在問(wèn)題,在多線程的情況下,可能會(huì)出現(xiàn)空指針問(wèn)題,出現(xiàn)問(wèn)題的原因是JVM在實(shí)例化對(duì)象的時(shí)候會(huì)進(jìn)行優(yōu)化和指令重排序操作。
要解決雙重檢查鎖模式帶來(lái)空指針異常的問(wèn)題,只需要使用?
volatile
?關(guān)鍵字,?volatile
?關(guān)鍵字可以保證可見(jiàn)性和有序性。
指令重排序問(wèn)題解釋
在多線程情況下,指令重排序可能會(huì)導(dǎo)致該模式的失效。當(dāng)一個(gè)線程搶到鎖之后,由于指令重排序的影響,實(shí)例變量可能會(huì)先被賦值到內(nèi)存中,但是還沒(méi)有調(diào)用構(gòu)造函數(shù),而此時(shí)另一個(gè)線程進(jìn)入該方法,會(huì)認(rèn)為instance不為空直接返回實(shí)例,但是此時(shí)實(shí)例并沒(méi)有完成初始化,會(huì)導(dǎo)致程序出錯(cuò)。
為了避免這種情況,需要在instance前添加volatile關(guān)鍵字,保證它能正確的被初始化。這樣可以確保其他線程在獲取instance的時(shí)候,總是從主內(nèi)存中獲取,而不是線程的本地內(nèi)存中獲取,從而避免了指令重排序帶來(lái)的問(wèn)題。
package com.yanyu.Singleton;
public class Singleton {
//私有構(gòu)造方法
private Singleton() {}
private static volatile Singleton instance;
//對(duì)外提供靜態(tài)方法獲取該對(duì)象
public static Singleton getInstance() {
//第一次判斷,如果instance不為null,不進(jìn)入搶鎖階段,直接返回實(shí)際
if(instance == null) {
synchronized (Singleton.class) {
//搶到鎖之后再次判斷是否為空
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
?這是一種線程安全的懶漢式單例模式實(shí)現(xiàn)方式,主要特點(diǎn)有:
1. 構(gòu)造函數(shù)私有化,確保該類不能在外部通過(guò)構(gòu)造函數(shù)來(lái)創(chuàng)建對(duì)象。
2. 使用 volatile 關(guān)鍵字修飾 instance,保證在多線程環(huán)境下 instance 的可見(jiàn)性。
3. getInstance 方法加入雙重檢查鎖,確保在多線程環(huán)境下只有一個(gè)線程能夠創(chuàng)建 Singleton 實(shí)例,并且只有在 instance 為 null 的情況下才會(huì)創(chuàng)建實(shí)例。
4. 使用 synchronized 關(guān)鍵字對(duì) instance 進(jìn)行加鎖,保證在多線程環(huán)境下只有一個(gè)線程能夠進(jìn)入創(chuàng)建實(shí)例的代碼塊。
5. 返回 Singleton 實(shí)例的方法為靜態(tài)方法,能夠在類的外部方便地獲取該對(duì)象。
這種實(shí)現(xiàn)方式既保證了線程安全,又減少了同步的開(kāi)銷,是一種比較常用的單例模式實(shí)現(xiàn)方式。
懶漢式-方式4(靜態(tài)內(nèi)部類方式)
靜態(tài)內(nèi)部類單例模式中實(shí)例由內(nèi)部類創(chuàng)建,由于 JVM 在加載外部類的過(guò)程中, 是不會(huì)加載靜態(tài)內(nèi)部類的, 只有內(nèi)部類的屬性/方法被調(diào)用時(shí)才會(huì)被加載, 并初始化其靜態(tài)屬性。靜態(tài)屬性由于被?static
?修飾,保證只被實(shí)例化一次,并且嚴(yán)格保證實(shí)例化順序。
package com.yanyu.Singleton;
public class Singleton {
//私有構(gòu)造方法
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
//對(duì)外提供靜態(tài)方法獲取該對(duì)象
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
這是一種利用靜態(tài)內(nèi)部類實(shí)現(xiàn)的線程安全的單例模式。在類加載器加載類的時(shí)候,靜態(tài)內(nèi)部類 SingletonHolder 不會(huì)被初始化,只有在 getInstance() 方法第一次被調(diào)用時(shí),才會(huì)被加載進(jìn)內(nèi)存并實(shí)例化,同時(shí)保證了線程安全。這種方式既保證了線程安全,也避免了同步帶來(lái)的性能損失,同時(shí)也實(shí)現(xiàn)了延遲加載。由于這種方式不需要加鎖,因此效率比較高。
?這種方式的核心思想是使用靜態(tài)內(nèi)部類來(lái)持有單例實(shí)例,因?yàn)?span style="color:#fe2c24;">靜態(tài)內(nèi)部類只會(huì)被加載一次,所以它的成員變量也只會(huì)被初始化一次,從而保證了線程安全。同時(shí),通過(guò)將單例對(duì)象的實(shí)例化延遲到內(nèi)部類加載時(shí)進(jìn)行,也實(shí)現(xiàn)了懶加載。
?枚舉方式
枚舉類實(shí)現(xiàn)單例模式是極力推薦的單例實(shí)現(xiàn)模式,因?yàn)槊杜e類型是線程安全的,并且只會(huì)裝載一次,設(shè)計(jì)者充分的利用了枚舉的這個(gè)特性來(lái)實(shí)現(xiàn)單例模式,枚舉的寫法非常簡(jiǎn)單,而且枚舉類型是所用單例實(shí)現(xiàn)中唯一一種不會(huì)被破壞的單例實(shí)現(xiàn)模式。
package com.yanyu.Singleton;
public enum Singleton {
INSTANCE;
}
?INSTANCE是一個(gè)枚舉對(duì)象,也就是該單例類的唯一實(shí)例。通過(guò)這種方式獲取單例對(duì)象,可以保證在任何情況下都只有一個(gè)實(shí)例對(duì)象存在。即使在多線程的情況下也是安全的。可以避免多線程問(wèn)題和反射攻擊
package com.yanyu.Singleton;
public class client{
public static void main(String[]args){
//創(chuàng)建singletion類的對(duì)象
Singleton instance = Singleton.INSTANCE;
Singleton instance1 = Singleton.INSTANCE;
//判斷獲取到的兩個(gè)是否是同一個(gè)對(duì)象
System.out.println(instance == instance1);
}
}
在使用枚舉類型實(shí)現(xiàn)單例模式時(shí),反射攻擊是無(wú)效的,因?yàn)槊杜e類型的構(gòu)造方法是私有的,并且只會(huì)在類加載時(shí)被調(diào)用一次,保證了單例的唯一性。在使用反射獲取該單例時(shí),會(huì)拋出異常,因?yàn)槊杜e類型不支持反射創(chuàng)建實(shí)例。因此,枚舉類型實(shí)現(xiàn)單例模式是一種安全可靠的方式。
1.4破壞單例模式的方式?
使上面定義的單例類(Singleton)可以創(chuàng)建多個(gè)對(duì)象,枚舉方式除外。有兩種方式,分別是序列化和反射。
序列化反序列化
Singleton類:
package com.yanyu.Singleton;
import java.io.Serializable;
public class Singleton implements Serializable {
//私有構(gòu)造方法
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
//對(duì)外提供靜態(tài)方法獲取該對(duì)象
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
Test類:
package com.yanyu.Singleton;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Test {
public static void main(String[] args) throws Exception {
//往文件中寫對(duì)象
//writeObject2File();
//從文件中讀取對(duì)象
Singleton s1 = readObjectFromFile();
Singleton s2 = readObjectFromFile();
//判斷兩個(gè)反序列化后的對(duì)象是否是同一個(gè)對(duì)象
System.out.println(s1 == s2);
}
private static Singleton readObjectFromFile() throws Exception {
//創(chuàng)建對(duì)象輸入流對(duì)象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Think\\Desktop\\a.txt"));
//第一個(gè)讀取Singleton對(duì)象
Singleton instance = (Singleton) ois.readObject();
return instance;
}
public static void writeObject2File() throws Exception {
//獲取Singleton類的對(duì)象
Singleton instance = Singleton.getInstance();
//創(chuàng)建對(duì)象輸出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Think\\Desktop\\a.txt"));
//將instance對(duì)象寫出到文件中
oos.writeObject(instance);
}
}
上面代碼運(yùn)行結(jié)果是false
,表明序列化和反序列化已經(jīng)破壞了單例設(shè)計(jì)模式。
當(dāng)單例模式中的實(shí)例被序列化成字節(jié)流并保存到文件系統(tǒng)或數(shù)據(jù)庫(kù)中時(shí),如果后續(xù)讀取對(duì)象并反序列化后,會(huì)生成一個(gè)新的實(shí)例,從而破壞了單例模式的原則。
這是因?yàn)椋谛蛄谢头葱蛄谢^(guò)程中,Java內(nèi)部使用了一個(gè)特殊的方法來(lái)創(chuàng)建對(duì)象,該方法不會(huì)調(diào)用任何構(gòu)造函數(shù),也不會(huì)檢查是否已經(jīng)存在該對(duì)象的實(shí)例。因此,如果單例模式本身沒(méi)有實(shí)現(xiàn)序列化和反序列化的特殊處理,就會(huì)導(dǎo)致破壞單例模式。
?序列化會(huì)將對(duì)象轉(zhuǎn)換為一個(gè)字節(jié)序列,以便在網(wǎng)絡(luò)上傳輸或保存到文件中。當(dāng)反序列化時(shí),會(huì)將字節(jié)序列轉(zhuǎn)換回對(duì)象。當(dāng)單例類被序列化為字節(jié)流時(shí),字節(jié)流中不包含單例類的狀態(tài),當(dāng)反序列化時(shí),新的實(shí)例會(huì)被創(chuàng)建出來(lái),這樣就違反了單例模式的原則,一個(gè)單例類就被破壞了。
反射?
Singleton類:
package com.yanyu.Singleton;
public class Singleton {
//私有構(gòu)造方法
private Singleton() {}
private static volatile Singleton instance;
//對(duì)外提供靜態(tài)方法獲取該對(duì)象
public static Singleton getInstance() {
if(instance != null) {
return instance;
}
synchronized (Singleton.class) {
if(instance != null) {
return instance;
}
instance = new Singleton();
return instance;
}
}
}
Test類:
package com.yanyu.Singleton;
import java.lang.reflect.Constructor;
public class Test {
public static void main(String[] args) throws Exception {
//獲取Singleton類的字節(jié)碼對(duì)象
Class clazz = Singleton.class;
//獲取Singleton類的私有無(wú)參構(gòu)造方法對(duì)象
Constructor constructor = clazz.getDeclaredConstructor();
//取消訪問(wèn)檢查
constructor.setAccessible(true);
//創(chuàng)建Singleton類的對(duì)象s1
Singleton s1 = (Singleton) constructor.newInstance();
//創(chuàng)建Singleton類的對(duì)象s2
Singleton s2 = (Singleton) constructor.newInstance();
//判斷通過(guò)反射創(chuàng)建的兩個(gè)Singleton對(duì)象是否是同一個(gè)對(duì)象
System.out.println(s1 == s2);
}
}
上面代碼運(yùn)行結(jié)果是
false
,表明反射已經(jīng)破壞了單例設(shè)計(jì)模式
?單例模式可以被反射機(jī)制破壞,因?yàn)榉瓷錂C(jī)制可以通過(guò)修改類的私有構(gòu)造方法來(lái)創(chuàng)建一個(gè)新的實(shí)例,這樣就違背了單例模式的原則。此外,反射還可以通過(guò)修改單例類中的字段來(lái)改變單例實(shí)例的狀態(tài),從而破壞單例模式的行為。
1.5?問(wèn)題的解決
序列化、反序列方式解決
在Singleton類中添加readResolve()
方法,在反序列化時(shí)被反射調(diào)用,如果定義了這個(gè)方法,就返回這個(gè)方法的值,如果沒(méi)有定義,則返回新new出來(lái)的對(duì)象。
Singleton類:
package com.yanyu.Singleton;
import java.io.Serializable;
public class Singleton implements Serializable {
//私有構(gòu)造方法
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
//對(duì)外提供靜態(tài)方法獲取該對(duì)象
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* //當(dāng),進(jìn)行反序列化時(shí),會(huì)白動(dòng)調(diào)用該力法,將該方法的返回值直接返回
*/
private Object readResolve() {
return SingletonHolder.INSTANCE;
}
}
當(dāng)反序列化時(shí),會(huì)先調(diào)用 readObject() 方法,如果該類中存在 readResolve() 方法,會(huì)在 readObject() 方法執(zhí)行之后,將 readResolve() 方法的返回值直接返回,從而確保只有一個(gè)實(shí)例。在該示例中,readResolve() 方法返回 SingletonHolder 的 INSTANCE,即單例對(duì)象的唯一實(shí)例。
反射方式解決
為了防止這種破解,可以在單例類的構(gòu)造函數(shù)中添加判斷,如果已存在實(shí)例,則拋出異常或返回已存在的實(shí)例。這樣,在通過(guò)反射調(diào)用私有構(gòu)造函數(shù)時(shí),就會(huì)拋出異?;蚍祷匾汛嬖诘膶?shí)例,從而阻止破解單例模式。
package com.yanyu.Singleton;
public class Singleton {
private static boolean flag = false;
//私有構(gòu)造方法
private Singleton() {
/*
反射破解單例模式需要添加的代碼
*/
synchronized (Singleton.class){
if (flag){
throw new RuntimeException("不能創(chuàng)建多個(gè)對(duì)象");
}
flag = true;
}
}
private static volatile Singleton instance;
//對(duì)外提供靜態(tài)方法獲取該對(duì)象
public static Singleton getInstance() {
if(instance != null) {
return instance;
}
synchronized (Singleton.class) {
if(instance != null) {
return instance;
}
instance = new Singleton();
return instance;
}
}
}
上述代碼中的防止反射破解的代碼可以阻止通過(guò)反射調(diào)用私有構(gòu)造方法創(chuàng)建多個(gè)實(shí)例。但是,如果通過(guò)反射修改了flag標(biāo)志位,就可以繞過(guò)這個(gè)限制,破壞單例模式。
為了進(jìn)一步防止通過(guò)反射破解單例模式,可以在getInstance方法中添加判斷,如果已存在實(shí)例,再次調(diào)用構(gòu)造方法時(shí),直接返回已存在的實(shí)例。這樣,即使通過(guò)反射修改了flag標(biāo)志位,也無(wú)法創(chuàng)建新的實(shí)例,從而保證單例模式的唯一性。
?1.6JDK源碼解析-Runtime類
?從上面源代碼中可以看出Runtime類使用的是惡漢式(靜態(tài)屬性)方式來(lái)實(shí)現(xiàn)單例模式的。
使用Runtime類中的方法
public class RuntimeDemo {
public static void main(String[] args) throws IOException {
//獲取Runtime類對(duì)象
Runtime runtime = Runtime.getRuntime();
?
//返回 Java 虛擬機(jī)中的內(nèi)存總量。
System.out.println(runtime.totalMemory());
//返回 Java 虛擬機(jī)試圖使用的最大內(nèi)存量。
System.out.println(runtime.maxMemory());
?
//創(chuàng)建一個(gè)新的進(jìn)程執(zhí)行指定的字符串命令,返回進(jìn)程對(duì)象
Process process = runtime.exec("ipconfig");
//獲取命令執(zhí)行后的結(jié)果,通過(guò)輸入流獲取
InputStream inputStream = process.getInputStream();
byte[] arr = new byte[1024 * 1024* 100];
int b = inputStream.read(arr);
System.out.println(new String(arr,0,b,"gbk"));
}
}
1.7應(yīng)用案例
- Windows 的 Task Manager(任務(wù)管理器)。
- Windows 的 Recycle Bin(回收站)。在整個(gè)系統(tǒng)運(yùn)行過(guò)程中,回收站一直維護(hù)著僅有的一個(gè)實(shí)例。
- 網(wǎng)站的計(jì)數(shù)器,一般也是采用單例模式實(shí)現(xiàn),否則難以同步。
- 應(yīng)用程序的日志應(yīng)用,一般都何用單例模式實(shí)現(xiàn),這一般是由于共享的日志文件一直處于打開(kāi)狀態(tài),因?yàn)橹荒苡幸粋€(gè)實(shí)例去操作,否則內(nèi)容不好追加。
- Web 應(yīng)用的配置對(duì)象的讀取,一般也應(yīng)用單例模式,這個(gè)是由于配置文件是共享的資源。
二、實(shí)驗(yàn)
任務(wù)描述
在企業(yè)網(wǎng)站后臺(tái)系統(tǒng)中,一般會(huì)將網(wǎng)站統(tǒng)計(jì)單元進(jìn)行獨(dú)立設(shè)計(jì),比如登錄人數(shù)的統(tǒng)計(jì)、IP 數(shù)量的計(jì)數(shù)等。在這類需要完成全局統(tǒng)計(jì)的過(guò)程中,就會(huì)用到單例模式,即整個(gè)系統(tǒng)只需要擁有一個(gè)計(jì)數(shù)的全局對(duì)象。
本關(guān)任務(wù):模擬網(wǎng)站登錄,高并發(fā)場(chǎng)景。模擬 10 個(gè)登錄線程,程序輸出登錄總數(shù)。
實(shí)現(xiàn)要點(diǎn)
- 在類中添加一個(gè)私有靜態(tài)成員變量用于保存單例實(shí)例。
- 聲明一個(gè)公有靜態(tài)構(gòu)建方法用于獲取單例實(shí)例。
- 在靜態(tài)方法中實(shí)現(xiàn)"延遲初始化"。 該方法會(huì)在首次被調(diào)用時(shí)創(chuàng)建一個(gè)新對(duì)象, 并將其存儲(chǔ)在靜態(tài)成員變量中。 此后該方法每次被調(diào)用時(shí)都返回該實(shí)例。
- 將類的構(gòu)造函數(shù)設(shè)為私有。 類的靜態(tài)方法仍能調(diào)用構(gòu)造函數(shù), 但是其他對(duì)象不能調(diào)用。
- 檢查客戶端代碼, 將對(duì)單例的構(gòu)造函數(shù)的調(diào)用替換為對(duì)其靜態(tài)構(gòu)建方法的調(diào)用。
編程要求
本任務(wù)有三個(gè)文件“Login.java”、“Client.java”和“Singleton.java”,在右側(cè)編輯器 Begin-End 內(nèi)補(bǔ)充 Singleton 中代碼,其它文件請(qǐng)閱讀代碼
Singleton.java?
package step1;
import java.util.concurrent.atomic.AtomicLong;
public class Singleton {
private static Singleton instance;
//AtomicLong是以原子方式操作long值的類,作用是保證并發(fā)時(shí)線程安全的累加
private AtomicLong count = new AtomicLong(0);
/********** Begin *********/
//此處增加Singleton的構(gòu)造函數(shù)
private Singleton() {
// 私有化構(gòu)造函數(shù),防止外部實(shí)例化
}
/********** End *********/
public static Singleton GetInstance(){
/********** Begin *********/
//考慮線程安全問(wèn)題
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
/********** End *********/
}
public AtomicLong getCount() {
return count;
}
public void setCount() {
count.addAndGet(1);
}
}
?Login.java文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-713513.html
package step1;
//Runnable 接口由其實(shí)現(xiàn)類來(lái)由線程執(zhí)行對(duì)應(yīng)的實(shí)例。對(duì)于實(shí)現(xiàn)類必須是實(shí)現(xiàn)方法 run
public class Login implements Runnable{
private String loginname;
public String getLoginname() {
return loginname;
}
public void setLoginname(String loginname) {
this.loginname = loginname;
}
@Override
public void run() {
Singleton lazySingleton =Singleton.GetInstance();
lazySingleton.setCount();
/*調(diào)試時(shí)觀察
System.out.println(getLoginname()+"登錄成功."+lazySingleton);
*/
}
}
Client.java文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-713513.html
package step1;
public class Client {
public final static int num = 10;
public static void main(String[] args) throws InterruptedException {
///創(chuàng)建10個(gè)線程,模擬10個(gè)用戶登錄
Thread[] threads = new Thread[num];
for (int i = 0; i < num; i++) {
Login login = new Login();
login.setLoginname("" + String.format("%2s", (i + 1)) + "號(hào)用戶");
//創(chuàng)建了線程threads[i],并把login對(duì)象(已實(shí)現(xiàn)Runnable接口)放入線程中
threads[i]=new Thread(login);
//線程狀態(tài)轉(zhuǎn)換為RUNNABLE
threads[i].start();
}
for (Thread thread : threads) {
//Client等待線程結(jié)束之后才能繼續(xù)運(yùn)行,防止最后一行的System.out.println提前運(yùn)行
thread.join();
}
System.out.println("網(wǎng)站共有"+Singleton.GetInstance().getCount()+"個(gè)用戶登錄");
}
}
到了這里,關(guān)于萬(wàn)字解析設(shè)計(jì)模式之單例模式的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!