餓漢式
立即加載,線程安全。
public class Singleton {
private static Singleton INSTANCE = new Singleton();//立即加載
private Singleton(){};
public static Singleton getInstance() {
return INSTANCE;
}
}
懶漢式
延遲加載,線程不安全。
public class Singleton {
private static Singleton INSTANCE;//延遲加載
private Singleton(){};
public static Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
雙檢鎖(DCL)
延遲加載,線程安全。
public class Singleton {
private volatile static Singleton INSTANCE;//延遲加載
private Singleton(){};
public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
靜態內部類
延遲加載,線程安全。
靜態內部類屬於被動引用,只有在調用時才加載。而JVM的類加載機制會保證只有一個線程成功執行<clinit>
方法,初始化類變量。
public class Singleton {
private Singleton(){}
private static class SingletonHolder {
private static Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
枚舉
延遲加載,線程安全。
public class Singleton {
private Singleton(){}
//靜態枚舉類
static enum SingletonHolder {
INSTANCE;
private Singleton singleton;
private SingletonHolder() {
singleton = new Singleton();
}
public Singleton getInstance() {
return singleton;
}
}
//對外暴露的獲取方法
public static Singleton getInstance() {
return SingletonHolder.INSTANCE.getInstance();
}
}
單例與反射
反射破壞單例:
public class Singleton {
private Singleton(){}
private static class SingletonHolder {
private static Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
public static void main(String[] args) throws Exception {
Class clazz = Singleton.class;
Constructor constructor = clazz.getDeclaredConstructor(null);
constructor.setAccessible(true);
Singleton o1 = (Singleton) constructor.newInstance();
Singleton o2 = (Singleton) constructor.newInstance();
System.out.println(o1);
System.out.println(o2);
System.out.println(o1 == o2);//false
}
}
一般而言有兩種解決方案:
一:在私有構造中設置標誌位,進行訪問次數的判斷,限制只能訪問一次構造函數。
public class Singleton {
private static boolean flag = false;
private Singleton(){
if (flag == false) {
flag = true;
} else {
throw new RuntimeException("反射嘗試破壞...");
}
}
private static class SingletonHolder {
private static Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
public static void main(String[] args) throws Exception {
Class clazz = Singleton.class;
Constructor constructor = clazz.getDeclaredConstructor(null);
constructor.setAccessible(true);
Singleton o1 = (Singleton) constructor.newInstance();
Singleton o2 = (Singleton) constructor.newInstance();
System.out.println(o1);
System.out.println(o2);
System.out.println(o1 == o2);
}
}
二:直接使用枚舉式單例,枚舉沒有無參構造,天然單例防反射。
單例與序列化
序列化單例對象,再反序列化也可以破壞單例模式:
public class Singleton implements Serializable{
private static boolean flag = false;
private Singleton(){
if (flag == false) {
flag = true;
} else {
throw new RuntimeException("反射嘗試破壞...");
}
}
private static class SingletonHolder {
private static Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
public static void main(String[] args) throws Exception {
Singleton source = Singleton.getInstance();
writeObject(source);//序列化
Singleton target = (Singleton) readObjcet();//反序列化
System.out.println(source);
System.out.println(target);
System.out.println(source == target);
}
public static void writeObject(Object o) {
FileOutputStream fileOut = null;
ObjectOutputStream out = null;
try {
fileOut = new FileOutputStream("test.txt");
out = new ObjectOutputStream(fileOut);
out.writeObject(o);
System.out.println("Serialized data is saved");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
fileOut.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static Object readObjcet() {
Object temp = null;
FileInputStream fileIn = null;
ObjectInputStream in = null;
try {
fileIn = new FileInputStream("test.txt");
in = new ObjectInputStream(fileIn);
temp = in.readObject();
System.out.println("Deserialized Object...");
return temp;
} catch (Exception e) {
e.printStackTrace();
return null;
}finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
fileIn.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
解決方案:添加readResolve()方法
,在方法中返回單例
public class Singleton implements Serializable{
private static boolean flag = false;
private Singleton(){
if (flag == false) {
flag = true;
} else {
throw new RuntimeException("反射嘗試破壞...");
}
}
private static class SingletonHolder {
private static Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
protected Object readResolve() throws ObjectStreamException{
System.out.println("調用了readReslove...");
return SingletonHolder.INSTANCE;
}
}
原理:在ObjectInputStream
類的readOrdinaryObject()
方法中,對readResolve()
方法的存在做了判斷,如果存在,則調用該方法獲取返回值作爲最終返回。