對 volatile 變量的寫操作晃听,不允許和它之前的讀寫操作打亂順序;
對 volatile 變量的讀操作砰识,不允許和它之后的讀寫亂序能扒。
public class Single {
private static volatile Single s= null;
private Single(){}//私有構(gòu)造方法,避免外部創(chuàng)建實(shí)例
public static Single getInstance() {
Single st = s; // 在這里創(chuàng)建臨時(shí)變量
if (st== null) {
synchronized (Single.class) {//靜態(tài)同步函數(shù)的鎖是(類.class)
st= s;
if (st== null) {
s=st= new Single();
}
}
}
return st; // 注意這里返回的是臨時(shí)變量
}
這里為什么需要再定義一個(gè)臨時(shí)變量st?通過前面的對 volatile 關(guān)鍵字作用解釋可知辫狼,訪問 volatile 變量初斑,需要保證一些執(zhí)行順序,所以的開銷比較大膨处。這里定義一個(gè)臨時(shí)變量见秤,在 s 不為空的時(shí)候(這是絕大部分的情況),只要在開始訪問一次 volatile 變量真椿,返回的是臨時(shí)變量鹃答。如果沒有此臨時(shí)變量,則需要訪問兩次突硝,而降低了效率测摔。通過這樣修改以后,這樣能提高 25% 的性能。wiki
關(guān)于單例模式锋八,還有一個(gè)更有趣的實(shí)現(xiàn)浙于,采用靜態(tài)內(nèi)部類,它能夠延遲初始化(lazy initialization)挟纱,并且多線程安全羞酗,還能保證高性能,如下:
class Singleton{
private Singleton(){}//私有構(gòu)造方法,避免外部創(chuàng)建實(shí)例
private static class SingletonHolder
{
public static final Singleton instance= new Singleton();
}
public static Singleton getInstance()
{
return SingletonHolder.instance;
}
}
利用內(nèi)部類延遲初始化樊销,這里是利用了 Java 的語言特性整慎,內(nèi)部類只有在使用的時(shí)候,才回去加載围苫,從而初始化內(nèi)部靜態(tài)變量裤园。關(guān)于線程安全,這是 Java 運(yùn)行環(huán)境自動(dòng)給你保證的剂府,在加載的時(shí)候拧揽,會自動(dòng)隱形的同步。在訪問對象的時(shí)候腺占,不需要同步 Java 虛擬機(jī)又會自動(dòng)給你取消同步淤袜,所以效率非常高。
在反射的作用下衰伯,單例結(jié)構(gòu)是會被破壞的铡羡,測試代碼如下所示
package test;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import singleton.LazySingleton2;
/**
* @author zhengrongjun
*/
public class LazySingleton2Test {
public static void main(String[] args) {
//創(chuàng)建第一個(gè)實(shí)例
LazySingleton2 instance1 = LazySingleton2.getInstance();
//通過反射創(chuàng)建第二個(gè)實(shí)例
LazySingleton2 instance2 = null;
try {
Class<LazySingleton2> clazz = LazySingleton2.class;
Constructor<LazySingleton2> cons = clazz.getDeclaredConstructor();
cons.setAccessible(true);
instance2 = cons.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
//檢查兩個(gè)實(shí)例的hash值
System.out.println("Instance 1 hash:" + instance1.hashCode());
System.out.println("Instance 2 hash:" + instance2.hashCode());
}
}
輸出如下
Instance 1 hash:1694819250
Instance 2 hash:1365202186
根據(jù)哈希值可以看出,反射破壞了單例的特性意鲸,因此懶漢式V3版誕生了:
package singleton;
public class LazySingleton3 {
private static boolean initialized = false;
private LazySingleton3() {
synchronized (LazySingleton3.class) {
if (initialized == false) {
initialized = !initialized;
} else {
throw new RuntimeException("單例已被破壞");
}
}
}
static class SingletonHolder {
private static final LazySingleton3 instance = new LazySingleton3();
}
public static LazySingleton3 getInstance() {
return SingletonHolder.instance;
}
}
此時(shí)再運(yùn)行一次測試類烦周,出現(xiàn)如下提示
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at test.LazySingleton3Test.main(LazySingleton3Test.java:21)
Caused by: java.lang.RuntimeException: 單例已被破壞
at singleton.LazySingleton3.<init>(LazySingleton3.java:12)
... 5 more
Instance 1 hash:359023572
這里就保證了,反射無法破壞其單例特性.
在分布式系統(tǒng)中怎顾,有些情況下你需要在單例類中實(shí)現(xiàn) Serializable 接口读慎。這樣你可以在文件系統(tǒng)中存儲它的狀態(tài)并且在稍后的某一時(shí)間點(diǎn)取出。讓我們測試這個(gè)懶漢式V3版在序列化和反序列化之后是否仍然保持單例槐雾。
先將
public class LazySingleton3
修改為
public class LazySingleton3 implements Serializable
上測試類如下
package test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import singleton.LazySingleton3;
public class LazySingleton3Test {
public static void main(String[] args) {
try {
LazySingleton3 instance1 = LazySingleton3.getInstance();
ObjectOutput out = null;
out = new ObjectOutputStream(new FileOutputStream("filename.ser"));
out.writeObject(instance1);
out.close();
//deserialize from file to object
ObjectInput in = new ObjectInputStream(new FileInputStream("filename.ser"));
LazySingleton3 instance2 = (LazySingleton3) in.readObject();
in.close();
System.out.println("instance1 hashCode=" + instance1.hashCode());
System.out.println("instance2 hashCode=" + instance2.hashCode());
} catch (Exception e) {
e.printStackTrace();
}
}
}
輸出如下
instance1 hashCode=2051450519
instance2 hashCode=1510067370
顯然夭委,我們又看到了兩個(gè)實(shí)例類。為了避免此問題募强,我們需要提供 readResolve() 方法的實(shí)現(xiàn)株灸。readResolve()代替了從流中讀取對象。這就確保了在序列化和反序列化的過程中沒人可以創(chuàng)建新的實(shí)例钻注。
因此蚂且,我們提供懶漢式V4版代碼如下
package singleton;
import java.io.Serializable;
public class LazySingleton4 implements Serializable {
private static boolean initialized = false;
private LazySingleton4() {
synchronized (LazySingleton4.class) {
if (initialized == false) {
initialized = !initialized;
} else {
throw new RuntimeException("單例已被破壞");
}
}
}
static class SingletonHolder {
private static final LazySingleton4 instance = new LazySingleton4();
}
public static LazySingleton4 getInstance() {
return SingletonHolder.instance;
}
private Object readResolve() {
return getInstance();
}
}
此時(shí),在運(yùn)行測試類幅恋,輸出如下
instance1 hashCode=2051450519
instance2 hashCode=2051450519
這表示此時(shí)已能保證序列化和反序列化的對象是一致的.