懶漢式
public class LazySingleton{
private LazySingleton instance;
private LazySingleton{}//構(gòu)造方法私有化
public static LazySingleton getInstance{
if(instance == null){
instance = new LazySingleton();
}
return instance;
}
}
之所以稱之為“懶漢式”,是因?yàn)樵谛枰搶?shí)例的時(shí)候才會(huì)去進(jìn)行創(chuàng)建的操作。對(duì)于多線程開發(fā),上面的單例模式就不能夠保證該類對(duì)象是單例了,因?yàn)槎嗑€程可能會(huì)同時(shí)進(jìn)入if語(yǔ)句內(nèi),從而都會(huì)去創(chuàng)建這個(gè)實(shí)例。
public class LazySingleton{
private LazySingleton instance;
private LazySingleton(){};
public static synchronized LazySingleton getInstance{
if(instance == null){
instance = new LazySingleton();
}
return instance;
}
}
我們通過給getInstance方法添加synchronized關(guān)鍵字(也就是加鎖),保證該方法一次只能被一個(gè)線程調(diào)用,從而避免了上述的多線程破壞單例的情況。但是這個(gè)方法也存在缺點(diǎn),就是當(dāng)instance已經(jīng)被初始化后再調(diào)用時(shí)每次都需要去做加鎖的操作,非常影響效率。
public class LazySingleton{
private LazySingleton instance;
private LazySingleton(){};
public static LazySingleton getInstance(){
if(instance == null){
synchronized(LazySingleton.class){
instance = new LazySingleton();
}
}
return instance;
}
}
我們直接將鎖從方法挪到實(shí)例化對(duì)象中去了,這樣的話當(dāng)instance不等于空的時(shí)候不用處理鎖,直接進(jìn)行返回。但是這種寫法仍然會(huì)有問題,當(dāng)多個(gè)線程同時(shí)進(jìn)入if語(yǔ)句時(shí),都會(huì)排隊(duì)獲取鎖并進(jìn)行實(shí)例化對(duì)象,從而又破壞了單例模式。那我們應(yīng)該怎么做呢?
public class LazySingleton{
private LazySingleton instance;
private LazySingleton(){};
public static LazySingleton getInstance(){
if(instance == null){ // 注釋1
synchronized(LazySingleton.class){
if(instance == null){
instance = new LazySingleton();//注釋2
}
}
}
return instance;
}
}
我們可以在實(shí)例化之前再做一次判斷,這樣第一次獲得所的線程實(shí)例化之后,后面獲得鎖的線程發(fā)現(xiàn)instance不為空,直接返回對(duì)象。但是這個(gè)方式仍然存在弊端,有可能會(huì)造成該對(duì)象不為空,但是不能被使用的情況。原因主要是因?yàn)閷?shí)例化對(duì)象的時(shí)候會(huì)有以下三個(gè)步驟:
- 在堆內(nèi)存中開辟空間;
- 初始化對(duì)象;
- 將該對(duì)象指向?qū)?yīng)的引用,引用賦值
而這3個(gè)步驟的順序不是固定的,有可能是1-2-3,也有可能是1-3-2,我們稱之為指令重排。那試想一下,第一個(gè)線程獲得鎖后,實(shí)例化對(duì)象的順序?yàn)?-3-2,當(dāng)剛執(zhí)行完3還沒有來得及執(zhí)行2的時(shí)候,第二個(gè)線程執(zhí)行第一個(gè)if語(yǔ)句,會(huì)發(fā)現(xiàn)instance此時(shí)不為空直接返回。但是這時(shí)instance還沒有被初始化,所以用instance進(jìn)行操作就會(huì)出現(xiàn)問題。那該如何解決呢,答案就是添加volatile關(guān)鍵字禁止這種指令重排,代碼如下:
public class LazySingleton{
private volatile LazySingleton instance;
private LazySingleton(){};
public static LazySingleton getInstance(){
if(instance == null){ // 注釋1
synchronized(LazySingleton.class){
if(instance == null){
instance = new LazySingleton();//注釋2
}
}
}
return instance;
}
}
餓漢式
public class HungrySingleton{
private static HungrySingleton instance = new HungrySingleton{};
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return instance;
}
}
當(dāng)調(diào)用HungrySingleton類的getInstance方法時(shí),JVM將HungrySingleton類加載到內(nèi)存中,通過類加載機(jī)制確保instance的唯一性。但是餓漢式存在一個(gè)問題,就是當(dāng)類一旦被加載,instance實(shí)例就會(huì)被創(chuàng)建,會(huì)造成不必要的內(nèi)存開銷。
靜態(tài)內(nèi)部類
public class InnerClassSingleton{
private static class InnerClassHolder{
private static InnerClassSingleton instance = new InnerClassSingleton();
}
private InnerClassSingleton(){}
public static InnerClassSingleton getInstance(){
return InnerClassHolder.instance;
}
}
靜態(tài)內(nèi)部類的這個(gè)方式相當(dāng)于是結(jié)合了懶漢式和餓漢式兩者的優(yōu)點(diǎn)。通過餓漢式一樣的類加載機(jī)制實(shí)現(xiàn)對(duì)象的唯一性,程序只有在調(diào)用getInstance方法時(shí)才會(huì)去加載InnerClassHolder類從而創(chuàng)建實(shí)例,避免加載InnerClassSingleton類時(shí)就創(chuàng)建對(duì)象,造成不必要的內(nèi)存開銷。
反射攻擊
上面的餓漢式和靜態(tài)內(nèi)部類實(shí)現(xiàn)的單例模式碰到反射機(jī)制就變得無效了,具體代碼如下:
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Test1 {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class objClass = InnerClassSingleton.class;
//獲取類的構(gòu)造器
Constructor constructor = objClass.getDeclaredConstructor();
//把構(gòu)造器私有權(quán)限放開
constructor.setAccessible(true);
//正常的獲取實(shí)例方式
InnerClassSingleton staticInnerClass = InnerClassSingleton.getInstance();
//反射創(chuàng)建實(shí)例
InnerClassSingleton newStaticInnerClass = (InnerClassSingleton) constructor.newInstance();
System.out.println(staticInnerClass);
System.out.println(newStaticInnerClass);
System.out.println(staticInnerClass == newStaticInnerClass);
}
}
上面的結(jié)果返回的是false,說明出現(xiàn)了兩個(gè)不同的實(shí)例,這就違反了我們的單例原則,不能保證只有一個(gè)實(shí)例了。那如何解決呢?
public class InnerClassSingleton{
private static class InnerClassHolder{
private static InnerClassSingleton instance = new InnerClassSingleton();
}
private InnerClassSingleton(){
if(InnerClassHolder.instance != null){
throw IllegalStateException();//在這里判斷instance是否為空,不為空直接拋出異常
}
}
public static InnerClassSingleton getInstance(){
return InnerClassHolder.instance;
}
}
對(duì)于通過類加載時(shí)創(chuàng)建對(duì)象實(shí)例的這種單例模式,靜態(tài)內(nèi)部類、餓漢式,可以通過上面的方式來防止反射攻擊。反之如果不是通過類加載時(shí)創(chuàng)建實(shí)例的方式就沒有效果了。舉個(gè)例子:
public class LazySingleton{
private volatile LazySingleton instance;
private LazySingleton(){
if(instance != null){
throw IllegalStateException();
}
};
public static LazySingleton getInstance(){
if(instance == null){ // 注釋1
synchronized(LazySingleton.class){
if(instance == null){
instance = new LazySingleton();//注釋2
}
}
}
return instance;
}
}
其實(shí)很好理解了,假設(shè)我們先通過反射創(chuàng)建對(duì)象,走到構(gòu)造方法的時(shí)候instance對(duì)象為null,因此會(huì)正常創(chuàng)建對(duì)象并不會(huì)拋出異常。然后通過調(diào)用getInstance方法時(shí),instance會(huì)重新實(shí)例化一次,從而就出現(xiàn)了兩個(gè)不同的實(shí)例了。
其中對(duì)象的序列化也能夠破壞單例模式,具體代碼如下:
public class SerSingleton implements Serializable {
2 private volatile static SerSingleton uniqueInstance;
3 private String content;
4 public String getContent() {
5 return content;
6 }
7
8 public void setContent(String content) {
9 this.content = content;
10 }
11 private SerSingleton() {
12 }
13
14 public static SerSingleton getInstance() {
15 if (uniqueInstance == null) {
16 synchronized (SerSingleton.class) {
17 if (uniqueInstance == null) {
18 uniqueInstance = new SerSingleton();
19 }
20 }
21 }
22 return uniqueInstance;
23 }
24
25
26 public static void main(String[] args) throws IOException, ClassNotFoundException {
27 SerSingleton s = SerSingleton.getInstance();
28 s.setContent("單例序列化");
29 System.out.println("序列化前讀取其中的內(nèi)容:"+s.getContent());
30 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("SerSingleton.obj"));
31 oos.writeObject(s);
32 oos.flush();
33 oos.close();
34
35 FileInputStream fis = new FileInputStream("SerSingleton.obj");
36 ObjectInputStream ois = new ObjectInputStream(fis);
37 SerSingleton s1 = (SerSingleton)ois.readObject();
38 ois.close();
39 System.out.println(s+"\n"+s1);
40 System.out.println("序列化后讀取其中的內(nèi)容:"+s1.getContent());
41 System.out.println("序列化前后兩個(gè)是否同一個(gè):"+(s==s1));
42 }
43
44 }
得到的結(jié)果為false,是兩個(gè)不同的實(shí)例,因此通過序列化對(duì)象的方式會(huì)破壞單例模式。那有什么辦法可以即保證單例、避免反射攻擊又能避免序列化呢? 答案就是枚舉了,代碼如下:
public enum SerEnumSingleton implements Serializable {
2 INSTANCE;
3 private String content;
4 public String getContent() {
5 return content;
6 }
7 public void setContent(String content) {
8 this.content = content;
9 }
10 private SerEnumSingleton() {
11 }
12
13 public static void main(String[] args) throws IOException, ClassNotFoundException {
14 SerEnumSingleton s = SerEnumSingleton.INSTANCE;
15 s.setContent("枚舉單例序列化");
16 System.out.println("枚舉序列化前讀取其中的內(nèi)容:"+s.getContent());
17 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("SerEnumSingleton.obj"));
18 oos.writeObject(s);
19 oos.flush();
20 oos.close();
21
22 FileInputStream fis = new FileInputStream("SerEnumSingleton.obj");
23 ObjectInputStream ois = new ObjectInputStream(fis);
24 SerEnumSingleton s1 = (SerEnumSingleton)ois.readObject();
25 ois.close();
26 System.out.println(s+"\n"+s1);
27 System.out.println("枚舉序列化后讀取其中的內(nèi)容:"+s1.getContent());
28 System.out.println("枚舉序列化前后兩個(gè)是否同一個(gè):"+(s==s1));
29 }
30 }
至此,我們常見的單例模式就說完了,包括餓漢式、懶漢式、靜態(tài)內(nèi)部類和枚舉4種方式。謝謝大家的支持?。。?/p>