单例模式的几种写法

单例模式的几种写法

1、懒汉模式

​ 懒汉模式,顾名思义就是很懒,要等到第一次调用的时候才创建。

public class Lazy {

 private static Lazy INSTANCE = null;

 private Lazy() {
 }

 public static Lazy getInstance() {
     if (INSTANCE == null) {
         INSTANCE = new Lazy();
     }
     return INSTANCE;
 }
}

​ 看代码,可以看出INSTANCE在创建的时候是线程不安全的,在多线程情况下会创建多个Lazy对象。

2、加锁的懒汉模式

​ 既然懒汉模式线程不安全,那就给它加个锁来保证线程安全。

public class Lazy {

 private static Lazy INSTANCE = null;

 private Lazy() {
 }

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

​ 在第一次创建的时候加个synchronized同步。那这样就没有问题了吗?

​ 我们来看看上面的new Lazy()JVM中发生了什么。理想的代码如下:

memory = allocate();        //1:分配对象的内存空间
ctorInstance(memory);       //2:初始化对象
INSTANCE = memory;         //3:使INSTANCE指向刚分配的内存地址

CPU会将不满足happens-before原则的指令进行重排——指令重排(Google一下就知道了)。所以上述指令可能会被分配成:

memory = allocate();        //1:分配对象的内存空间
INSTANCE = memory;          //3:使INSTANCE指向刚分配的内存地址
ctorInstance(memory);       //2:初始化对象

​ 上面代码可以看出,INSTANCE指向内存地址比初始化对象先行发生了,这样导致INSTANCE的值不为null。如果此时有另一个线程走到if (INSTANCE == null)判断时,此时INSTANCE不为空,但是未进行初始化,从而导致系统异常。

​ 如果能让CPU不对指令进行重排?使用volatile关键字,可以阻止写操作指令重排。

3、双重锁的懒汉模式

public class Lazy {

 private volatile static Lazy INSTANCE = null;

 private Lazy() {
 }
 
 public static Lazy getInstance() {
     if (INSTANCE == null) {
         synchronized (Lazy.class) {
             if (INSTANCE == null) {
                 INSTANCE = new Lazy();
             }
         }
     }
     return INSTANCE;
 }
}    

4、饿汉模式

public class Hungry {

 private static Hungry INSTANCE = new Hungry();

 private Hungry() {
 }

 public static Hungry getInstance() {
     return INSTANCE;
 }
}

​ 饿汉非常饿,所以在类的加载过程中就已经把对象实例好了。在类加载过程中,classloader 机制保证了加载类只有一个线程,这就意味着只有一个线程去创建实例,这种模式是天然的线程安全,但是也存在一个资源浪费的问题,如果INSTANCE一直没有使用,则是浪费。

5、 静态内部类模式

public class InnerStaticClass {

 private static class Holder {
     private static final InnerStaticClass INSTANCE = new InnerStaticClass();
 }

 private InnerStaticClass() {
 }

 public static InnerStaticClass getInstance() {
     return Holder.INSTANCE;
 }
}

​ 该模式与饿汉模式的区别在于用静态内部类来实例化单例,而静态内部类的加载是在第一次显示调用的时候才会进行加载,因此可以解决INSTANCE一直没有使用而带来的资源浪费问题。

6、 枚举模式

public enum EnumInstance {

 INSTANCE;
 
 public void printOut(){
     System.out.println("枚举模式");
 }
}

​ 这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。这种方式是 《Effective Java》 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值