单例模式实现方式详解

概念:

  单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式。

核心作用:

  保证一个类只有一个实例,并且提供一个访问访问该实例的全局访问点。

单例模式优点:

  单例模式只生成一个实例,减少了内存的开销,当一个对象的产生需要比较多的资源时,如读取配置,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式解决。

实现方式:

1)饿汉式
/**
* 优点:线程安全、调用效率高 缺点:不能延时加载
*/
public class HungrySingleton {
private HungrySingleton() {
}

private static HungrySingleton instance = new HungrySingleton(); //类初始化立即加载这个对象,天然线程安全

public static HungrySingleton getInstance() {
return instance;
}
}

要点:饿汉式单例代码中,static变量会在类加载时初始化,此时也不会涉及多个线程访问该对象的问题。而且JVM保证只会加载一次该类,肯定不会发生并发访问的问题,所以是线程安全的。
问题:如果getInstance()一直没有被调用,则会造成资源浪费!

2)懒汉式
/**
* 优点:线程安全,能延时加载 缺点:每次调用getInstance()都要同步,并发效率低
*/
public class LazySingleton {
private LazySingleton() {
}
private static LazySingleton instance = null;

public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}

要点:延迟加载 or 懒加载!真正用的时候才加载!
问题:资源利用率高了,但是每次调用getInstance()时都要同步,并发效率较低。

3)双重检测锁式
/**
* 优点:线程安全,能延时加载,执行效率高(只在第一次时加锁)
* 问题:由于编译器优化原因(指令重排序) 和JVM底层内部模型原因,偶尔会出问题
* 解决:可以通过在instance前面加上volatile修饰,禁止重排序
*/
public class DoubleCheckSingleton {
private DoubleCheckSingleton() {
}

//private static DoubleCheckSingleton instance = null; //由于编译器优化原因(指令重排序) 和JVM底层内部模型原因,偶尔会出问题
private volatile static DoubleCheckSingleton instance = null; //通过添加volatile修饰,可以禁止JVM优化重排序等

public static DoubleCheckSingleton getInstance() {
if (instance == null) {
synchronized (DoubleCheckSingleton.class) {
if (instance == null) {
instance = new DoubleCheckSingleton();
}
}
}
return instance;
}
}

要点:该模式将同步内容下放到了if内部,锁的范围变小,不必每次获取对象时都进行同步,只有第一次才同步,提高了执行的效率。
问题:由于编译器优化原因(指令重排序) 和JVM底层内部模型原因,偶尔会出问题。可以通过添加volatile修饰解决。

4)静态内部类实现方式
/**
* 优点:线程安全、调用效率高、能延时加载
*/
public class StaticInnerClassSingleton {
private StaticInnerClassSingleton() {
}

//只有真正调用getInstance(),才会加载静态内部类,进而实现了懒加载
private static class Singleton {
//final static保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证线程安全
private final static StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
}

public static StaticInnerClassSingleton getInstance() {
return Singleton.instance;
}
}
要点:兼备了并发高效和延时加载的优势

但是,以上四种线程安全的实现方式都有一个共同的缺点,就是可以利用反射或者反序列化来重复构建对象。


5)枚举方式实现
/**
* 优点:枚举本身就是单例模式,线程安全、调用效率高、可以避免反射和反序列化的漏洞 缺点:没有懒加载
*/
public enum Singleton {
INSTANCE;
Singleton() {
}
}

如何选用?
 * 单例对象 占用 资源少, 不需要 延时加载:枚举式 好于 饿汉式
 * 单例对象 占用 资源多, 需要 延时加载:静态内部类式 好于 懒汉式

猜你喜欢

转载自www.cnblogs.com/jackspan/p/9504957.html