单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。
饿汉式
饿汉式在类加载时已经创建好该对象,在程序调用时直接返回该单例对象即可,即我们在编码时就已经指明了要马上创建这个对象,不需要等到被调用时再去创建。
public class Singleton{
private static final Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getInstance() {
return singleton;
}
}
懒汉式
1.基础---懒汉式创建对象的方法是在程序使用对象前,先判断该对象是否已经实例化(判空),若已实例化直接返回该类对象。否则则先执行实例化操作。
2.进阶--多线程同事判断为空,只创建一个对象
3.大师--如果没有实例化对象则加锁创建,如果已经实例化了,则不需要加锁,直接获取实例
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
/**
* 基础
*/
public static Singleton getInstanceL() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
/**
* 进阶
*/
public static synchronized Singleton getInstanceM1() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
// 或者
public static Singleton getInstanceM2() {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
return singleton;
}
/**
* 高手
*/
public static Singleton getInstanceH() {
// 线程A和线程B同时看到singleton = null,如果不为null,则直接返回singleton
if (singleton == null) {
// 线程A或线程B获得该锁进行初始化
synchronized (Singleton.class) {
// 其中一个线程进入该分支,另外一个线程则不会进入该分支
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
使用volatile防止指令重排并保证其内存可见性,即每一时刻线程读取到该变量的值都是内存中最新的那个值,线程每次操作该变量都需要先读取该变量。
/**大师*/
public class Singleton {
private static volatile Singleton singleton;
private Singleton(){}
public static Singleton getInstance() {
// 线程A和线程B同时看到singleton = null,如果不为null,则直接返回singleton
if (singleton == null) {
// 线程A或线程B获得该锁进行初始化
synchronized(Singleton.class) {
// 其中一个线程进入该分支,另外一个线程则不会进入该分支
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
破坏懒汉式单例与饿汉式单例
1.利用反射
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
}
2:利用序列化与反序列化破坏单例模式
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
}
枚举实现单例
/**王者*/
public enum Singleton {
INSTANCE(123, "ABC");
private Integer id;
private String name;
Singleton(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Singleton t1 = Singleton.INSTANCE;
t1.setName("DEF");
Singleton t2 = Singleton.INSTANCE;
t2.setName("XYZ");
System.out.println(t1);
System.out.println(t2);
System.out.print(t1 == t2);
(1)Enum 类内部使用Enum 类型判定防止通过反射创建多个对象
(2)Enum 类通过写出(读入)对象类型和枚举名字将对象序列化(反序列化),通过 valueOf() 方法匹配枚举名找到内存中的唯一的对象实例,防止通过反序列化构造多个对象
(3)枚举类不需要关注线程安全、破坏单例和性能问题,因为其创建对象的时机与饿汉式单例有异曲同工之妙。