一、懒汉式
/**
* 懒汉式
* 优点:需要的时候才把对象创建出来
* 缺点:由于加了 synchronized 锁,所以并发时效率较低
*/
public class Singleton1 {
private static Singleton1 singleton;
private Singleton1() {}
public static synchronized Singleton1 getInstance() {
if (singleton == null) {
singleton = new Singleton1();
}
return singleton;
}
}
二、饿汉式
/**
* 饿汉式
* 优点:没有加锁,性能高
* 缺点:在类装载时就实例化,浪费内存
*/
public class Singleton2 {
private static Singleton2 singleton = new Singleton2();
private Singleton2() {}
public static Singleton2 getInstance() {
return singleton;
}
}
三、双重校验锁
/**
* 双重校验锁
* 优点:需要的时候才创建对象,由于获取对象方法没有加锁,所以性能较高
* 缺点:实现较复杂
*/
public class Singleton3 {
private static Singleton3 singleton;
private Singleton3() {}
public static Singleton3 getInstance() {
if (singleton == null) {
synchronized (Singleton3.class) {
if (singleton == null) {
singleton = new Singleton3();
}
}
}
return singleton;
}
}
四、枚举
public enum Singleton4 {
INSTANCE;
}
优势 1:一目了然的代码
优势 2:天然的线程安全与单一实例
它不需要做任何额外的操作,就可以保证对象单一性与线程安全性。
优势 3:枚举保护单例模式不被破坏
使用枚举可以防止调用者使用反射、序列化与反序列化机制强制生成多个单例对象,破坏单例模式。
防反射
枚举类默认继承了 Enum 类,在利用反射调用 newInstance() 时,会判断该类是否是一个枚举类,如果是,则抛出异常。
防止反序列化创建多个枚举对象
在读入 Singleton 对象时,每个枚举类型和枚举名字都是唯一的,所以在序列化时,仅仅只是对枚举的类型和变量名输出到文件中,在读入文件反序列化成对象时,使用 Enum 类的 valueOf(String name) 方法根据变量的名字查找对应的枚举对象。
所以,在序列化和反序列化的过程中,只是写出和读入了枚举类型和名字,没有任何关于对象的操作。
五、静态内部类
/**
* 静态内部类
* 优点:实现简单,需要使用时才加载,节约内存
*/
public class Singleton5 {
private static class SingletonHolder {
private static final Singleton5 INSTANCE = new Singleton5();
}
private Singleton5() {}
public static Singleton5 getInstance() {
return SingletonHolder.INSTANCE;
}
}
破坏单例的方式
利用反射破坏单例模式
利用反射,强制访问类的私有构造器,去创建另一个对象
public static void main(String[] args) {
// 获取类的显式构造器
Constructor<Singleton> construct = Singleton.class.getDeclaredConstructor();
// 可访问私有构造器
construct.setAccessible(true);
// 利用反射构造新对象
Singleton obj1 = construct.newInstance();
// 通过正常方式获取单例对象
Singleton obj2 = Singleton.getInstance();
System.out.println(obj1 == obj2); // false
}
利用序列化与反序列化破坏单例模式
使用序列化和反序列化破坏单例模式
public static void main(String[] args) {
// 创建输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Singleton.file"));
// 将单例对象写到文件中
oos.writeObject(Singleton.getInstance());
// 从文件中读取单例对象
File file = new File("Singleton.file");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Singleton newInstance = (Singleton) ois.readObject();
// 判断是否是同一个对象
System.out.println(newInstance == Singleton.getInstance()); // false
}