0%

单例模式

作为一种创建型设计模式,单例模式能够保证一个类只有一个实例。

单例模式思路:

  1. 将默认的构造函数设为私有。
  2. 使用静态方法getInstance方法获取实例对象。

实现方案

单例模式可分成两种:

  1. 饿汉式:类加载时创建实例对象
  2. 饿汉式:类加载时不会创建实例对象,首次使用时才会创建

饿汉式

静态变量实现

public class SingleTon {
//私有构造方法
private SingleTon() {
}
// 静态实例对象,初始化时创建
private static final SingleTon instance = new SingleTon();
//public的静态方法来获取实例
public static SingleTon getInstance() {
return instance;
}
}

静态代码块方式

public class SingleTon {
private SingleTon(){}
private static final SingleTon instance;
//静态代码块在类加载时执行,注意与构造代码块的区别。
static {
instance = new SingleTon();
}
public static SingleTon getInstance(){
return instance;
}
}

枚举方式⭐

public enum Singleton{
INSTANCE;
}

借助枚举类的线程安全和只会装载一次的特性,实现了单例中唯一一种不会被破坏的单例模式。

饿汉式在没有被使用时就已经创建了,会造成内存浪费问题。

懒汉式

线程不安全实现方式


public class SingleTon {
private SingleTon(){}
//线程不安全
private static SingleTon instance;
public static SingleTon getInstance(){
if(instance==null)
instance = new SingleTon();
return instance;
}

线程安全的实现方式

public class SingleTon {
private SingleTon(){}
//线程安全
private static SingleTon instance;
public static synchronized SingleTon getInstance(){
if(instance==null)
instance = new SingleTon();
return instance;
}
}

使用synchronized保证并发场景下的线程安全,缺点也很明显,性能差。

双重检查实现线程安全

public class SingleTon {

private SingleTon(){}
//双重检查
private static volatile SingleTon instance; //防止指令重排
public static synchronized SingleTon getInstance(){
if(instance==null){
synchronized (SingleTon.class){
if(instance == null)
instance = new SingleTon();
}
}
return instance;
}
}

双重检查锁可以降低锁的粒度,因为大多数事件instance是不为null的,因此第一次检查不使用synchronize,如果第一次检查是null,才需要使用锁。注意要使用volatile来防止指令重排。

静态内部类实现

//静态内部类实现懒汉式单例模式,由jvm保证线程安全性
public class SingleTon {
private SingleTon(){};
private static class SingleTonHolder{
private static final SingleTon singleTon = new SingleTon();
}
public static SingleTon getInstance(){
return SingleTonHolder.singleTon;
}
}

JVM加载外部类的过程中,不会加载静态内部类,只有在静态内部类的属性和方法被调用时才会被加载,可以保证线程安全和唯一性。(由JVM保证)

破坏单例

序列化与反序列化

通过对单例对象的序列化再反序列化可破坏单例模式。

 //序列化破坏单例
try{
SingleTon singleTon = SingleTon.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("./test.txt"));
oos.writeObject(singleTon);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("./test.txt"));
SingleTon singleTon2 = (SingleTon) ois.readObject();
System.out.println(singleTon2==singleTon);
}
catch (Exception e){
System.out.println(e);
}

解决方法:增加readResolve()方法,在反序列化时如果发现存在该方法将直接返回原对象。

public Object readResolve(){
return instance;
}

反射

通过反射机制可以获得单例类的构造方法,进而更改构造方法的访问性,借此可在外部new 新对象。

try{
Class singleTonClass = SingleTon.class;
Constructor constructor = singleTonClass.getDeclaredConstructor();
constructor.setAccessible(true);
SingleTon singleTon1 = (SingleTon) constructor.newInstance();
SingleTon singleTon2 = (SingleTon) constructor.newInstance();
System.out.println(singleTon1 == singleTon2);
}
catch (NoSuchMethodException e){
System.out.println(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}

解决方法:在构造方法中判断是否已经存在instance了,如果存在则直接抛出异常。

private SingleTon(){
if(instance !=null){
throw new RuntimeException();
}
};