简单定义:单例模式在设计模式中属于创建式模式,要保证一个类只有一个实例,并且提供一个可以访问它的全局变量。
单例模式的原则:
1. 私有构造 (防止类通过常规方法实例化)
2. 以静态方法或者枚举返回实例。 (保证实例的唯一性)
3. 确保实例只有一个,尤其是多线程环境。(保证创建实例的线程安全)
4. 确保反序列化时不会重新构造对象。(在有序列化反序列化的场景下防止单例被莫名破坏)
单例模式实现方法的总结
懒汉式写法
package com.b_single;
class People {
private static People people = null;
//1.对构造方法私有化
private People () {
}
//2.写一个静态方法 使用类名直接调用的 不能使用对象来调用的 因为私有化构造方法了
public static People getInstance () {
if (people == null) {
people = new People();
}
return people;
}
}
public class Demo1 {
public static void main(String[] args) {
//当一new就会新建对象,创建唯一的对象,对当前类的无参构造方法加private修饰
People people = People.getInstance();
People people1 = People.getInstance();
System.out.println(people);
System.out.println(people1);
}
}
饿汉式写法
package com.b_single;
class SingleCat {
private static final SingleCat singleCat = new SingleCat();
private SingleCat() {
}
public static SingleCat getInstance() {
return singleCat;
}
}
public class Demo3 {
public static void main(String[] args) {
}
}
懒汉式-线程安全
只需要对 getUniqueInstance() 方法加锁,那么在一个时间点只能有一个线程能够进入该方法,从而避免了多次实例化 uniqueInstance 的问题。
public static synchronized Singleton getUniqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
但是当一个线程进入该方法之后,其它试图进入该方法的线程都必须等待,因此性能上有一定的损耗。
使用类的内部类(线程安全)
public class Singleton_04 {
private static class SingletonHolder {
private static Singleton_04 instance = new Singleton_04();
}
private Singleton_04() {
}
public static Singleton_04 getInstance() {
return SingletonHolder.instance;
}
}
使用类的静态内部类实现的单例模式,既保证了线程安全有保证了懒加载,同时不会因为加 锁的方式耗费性能。 这主要是因为JVM虚拟机可以保证多线程并发访问的正确性,也就是⼀个类的构造方法在多线 程环境下可以被正确的加载。 此种方式也是非常推荐使用的⼀种单例模式。
双重校验锁-线程安全 uniqueInstance
只需要被实例化一次,之后就可以直接使用了。加锁操作只需要对实例化那部分 的代码进行,只有当 uniqueInstance 没有被实例化时,才需要进行加锁。 双重校验锁先判断 uniqueInstance 是否已经被实例化,如果没有被实例化,那么才对实例化语句 进行加锁。
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
枚举类型实现
单例模式最佳实践,实现简单,并且能面对复杂序列化或者反射攻击时,防止实例化多次。
public enum Singleton {
uniqueInstance;
}
优点:线程安全,自由串行化,单一实例
缺点:存在继承场景下是不能使用的
考虑以下单例模式的实现,该 Singleton 在每次序列化的时候都会创建一个新的实例,为了保证只 创建一个实例,必须声明所有字段都是 transient,并且提供一个 readResolve() 方法。
public class Singleton implements Serializable {
private static Singleton uniqueInstance;
private Singleton() {
}
public static synchronized Singleton getUniqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
如果不用枚举类型实现单例模式,会出现反射攻击,因为通过setAccessible() 方法可以将私有构造 函数的访问权限从private修改成public,然后调用构造函数从而实例化对象,如果要防止这样的攻击, 需要在构造函数中添加实例化第二个对象的代码 从上面的讨论可以看出,解决序列化和反射攻击很麻烦,而枚举实现不会出现这两种问题,所以说 枚举实现单例模式是最佳实践。