常见的单例模式:饿汉式、懒汉式、双重检查锁模式、静态内部类实现单例模式、枚举单例模式,本文重点是在项目中如何实现上述的单例模式。
1. 饿汉式单例模式
饿汉式单例:类初始化时将单例对象加载到JVM中。
/**
* 饿汉单例模式。
* 类初始化时将单例对象加载到JVM中。
*/
@Slf4j
public class Singletoneh {
private final static Singletoneh instance = new Singletoneh();
private Singletoneh() {
}
public static Singletoneh getInstance() {
return instance;
}
public void say() {
System.out.println("【饿汉模式】—实现单例!");
}
}
2. 懒汉式单例模式
懒汉式单例:并发写时,存在线程安全问题。进化版:双重检查锁模式。
/**
* 懒汉式单例
*/
public class Singletonlh {
private static Singletonlh instance;
private Singletonlh() {
}
public static Singletonlh getInstance() {
if (instance == null) {
instance = new Singletonlh();
}
return instance;
}
public void say() {
System.out.println("【懒汉模式】—实现单例!");
}
}
3. 双重检查锁模式
volatile关键字详见—Volatile可见性原理
/**
* 双重检查锁模式
* volatile 关键字:防止指令重排
* <p>
* 被volatile修饰的变量,会加一个lock前缀的汇编指令。
* 若变量被修改后,会立刻将变量由工作内存回写到主存中。那么意味了之前的操作已经执行完毕。这就是内存屏障。
*/
public class SingletonOfSync2 {
private static volatile SingletonOfSync2 instance;
private SingletonOfSync2() {
}
/**
* 双重检查模式,防止并发写时创建多个实例对象。
* 使用volatile关键字防止指令重排;
*
* @return
*/
public static SingletonOfSync2 getInstance() {
if (instance == null) {
synchronized (SingletonOfSync2.class) {
if (instance == null) {
instance = new SingletonOfSync2();
}
}
}
return instance;
}
public void say() {
System.out.println("【双重检查锁模式】—实现单例!");
}
}
4. 静态内部类单例模式
由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。静态属性由static修饰,保证只被实例化一次,并且严格保证实例化顺序。
/**
* 静态内部类实现单例模式
* <p>
* 由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。
* 静态属性由static修饰,保证只被实例化一次,并且严格保证实例化顺序。
*/
public class SingletonOfInner {
private SingletonOfInner() {
}
private static class InstanceHolder {
private final static SingletonOfInner instance = new SingletonOfInner();
}
public static SingletonOfInner getInstance() {
return InstanceHolder.instance;
}
public void say() {
System.out.println("【静态内部类模式】—实现单例!");
}
}
5. 枚举类单例模式
因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式。
public class SingletonOfEnum {
//私有构造方法
private SingletonOfEnum() {
}
/**
* 枚举类返回单例对象
*/
public static SingletonOfEnum getInstance() {
return Singleton.INSTANCE.getInstance();
}
private enum Singleton {
INSTANCE;
private final SingletonOfEnum instance;
Singleton() {
instance = new SingletonOfEnum();
}
private SingletonOfEnum getInstance() {
return instance;
}
}
public void say() {
System.out.println("【枚举模式】—实现单例!");
}
}
6. 破坏单例模式以及解决方案
6.1 反射破坏
public class testSingleton {
public static void main(String[] args) throws Exception {
Constructor<Singletoneh> co1 = Singletoneh.class.getDeclaredConstructor();
co1.setAccessible(true);
Singletoneh s1 = co1.newInstance();
s1.say();
Constructor<Singletoneh> co2 = Singletoneh.class.getDeclaredConstructor();
co2.setAccessible(true);
Singletoneh s2 = co2.newInstance();
s2.say();
System.out.println("单例对象是否相等:" + (s1 == s2));
}
}

除枚举方式外, 其他方法都会通过反射的方式破坏单例,反射是通过调用构造方法生成新的对象,所以如果我们想要阻止单例破坏,可以在构造方法中进行判断,若已有实例, 则阻止生成新的实例,解决办法如下:
private Singletoneh() {
if(instance!=null){
throw new RuntimeException("单例对象已经存在");
}
}
6.2 序列化接口Serializable
如果单例类实现了序列化接口Serializable, 就可以通过反序列化破坏单例,所以我们可以不实现序列化接口,如果非得实现序列化接口,可以重写反序列化方法readResolve(), 反序列化时直接返回相关单例对象。
public Object readResolve() throws ObjectStreamException {
return instance;
}
网友评论