Java 单例模式

首先扯点别的:距离上次区华东理工玩已经很长时间了,等到8月份再去看看。

今天记录一下Java中单例模式的写法

单例模式:确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例

饿汉式单例类.在类初始化时,已经自行实例化,线程安全。

public class Singleton {

    private static final Singleton instance = new Singleton();

    private Singleton() {

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

这种方式在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快。 这种方式基于类加载机制避免了多线程的同步问题。

懒汉式单例类.在第一次调用的时候实例化自己。

  1. 同步方法
public class Singleton {  

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

这种写法能够在多线程中很好的工作,但是每次调用getInstance方法时都需要进行同步,造成不必要的同步开销,而且大部分时候我们是用不到同步的,所以不建议用这种模式。

  1. 双重检查
    //volatile关键字在这里是为了避免指令重排
    private volatile static Singleton INSTANCE;

    private Singleton() {

    }

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                	//注释1处,
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }

双重检查方式:第一次判断INSTANCE是否为null,是为了不必要的同步。第二次判断是在INSTANCE等于null的情况下才创建实例。比如有三个线程调用getInstance()方法,有可能三个调用都进入到了第一个if判断了,只有一个调用可以获得锁,如果不再判断一次INSTANCE 是否为空(第二个if),那么每一个获得锁的线程都会创建一个新的INSTANCE 对象,那么三个线程就会创建三个对象了,使用了if判断后,就保证只会创建一个对象。
volatile关键字的作用:一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义。

  • 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,变量的新值对其他线程来说是立即可见的。
  • 禁止进行指令重排。

我认为在这里使用volatile修饰共享变量是为了禁止指令重排。

在双重检查的注释1处,可以分为3步。

  1. 在内存中开辟一块地址。
  2. 在地址上初始化Singleton对象。
  3. 将INSTANCE引用指向Singleton对象地址。

根据双重检查单例为什么要加volatile一文中有序性的分析;在双重检查的注释1处的代码执行顺序会有:1—>2—>3和1—>3—>2 两种情况,只要最终结果一致,在java内存中以上执行顺序都有可能发生。
问题就出在执行顺序上,1—>2—>3顺序执行不会有什么问题;如果执行1—>3—>2,我们推测会有什么情况发生:
如果A线程执行完了注释1处,B线程获取锁了,这个时候B线程发现INSTANCE不为null了,就直接返回INSTANCE。此时,如果执行顺序是1—>3—>2时,INSTANCE拿到对象地址,对象正在初始化中;
B线程获取锁返回INSTANCE,此时INSTANCE对象还未初始化完成或部分部分初始化,如果这个时候使用INSTANCE进行其他操作,会导致空指针或操作与预期结果不一致问题。
在INSTANCE前加volatile关键字,就能确保执行顺序为 1—>2—>3。

最终结论,双重检查单例利用了volatile的能禁止指令重排序特性,保证代码的有序性。

参考
单例模式singleton为什么要加volatile
双重检查单例为什么要加volatile

2 静态内部类方式。类只会加载一次。

    private Singleton() {

    }

    private static class LazyLoader {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return LazyLoader.INSTANCE;
    }

3 枚举方法

public enum Singleton {

    INSTANCE;

    public void method(){
        System.out.println(" I am singleton");
    }

}

使用枚举除了线程安全和防止反射强行调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象.因此,《Effective Java》推荐尽可能地使用枚举来实现单例。

参考链接

  1. 设计模式(二)单例模式的七种写法
  2. Java并发编程:volatile关键字解析
  3. 单例模式singleton为什么要加volatile
  4. 双重检查单例为什么要加volatile
  5. Java并发编程(三)volatile域
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值