单例模式

本文详细介绍了单例模式的五种实现方式,包括懒汉式、饿汉式、同步、双重校验锁和静态内部类。每种方式都有其特点和适用场景,帮助读者理解如何在不同需求下选择合适的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    单例模式(singleton),一个类有且仅有一个实例,并且自行实例化向整个系统提供。

    保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。

1、懒汉式
    lazy loaded,迟加载——直到使用的时候才进行加载。不适宜多线程,线程不安全。
public class SingletonClass{
    private static SingletonClass instance = null;

    private SingletonClass(){
    }

    public static SingletonClass getInstance(){
        if(instance == null){
               instance=new SingletonClass();
        }
        return instance;
    }
}
   线程不安全,如:线程A希望使用SingletonClass,调用getInstance()方法。因为是第一次调用,A就发现instance是null的,于是它开始创建实例,就在这个时候,CPU发生时间片切换,线程B开始执行,它要使用SingletonClass,调用getInstance()方法,同样检测到instance是null——注意,这是在A检测完之后切换的,也就是说A并没有来得及创建对象——因此B开始创建。B创建完成后,切换到A继续执行,因为它已经检测完了,所以A不会再检测一遍,它会直接创建对象。这样,线程A和B各自拥有一个SingletonClass的对象——单例失败!

2、饿汉式
    基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载。
public class SingletonClass{  
     private static SingletonClass instance = new SingletonClass();  
     private SingletonClass(){
     }  
     public static SingletonClass getInstance(){  
           return instance;  
     }  
}  

3、同步
     给getInstance()加上同步锁,一个线程必须等待另外一个线程创建完成后才能使用这个方法,这就保证了单例的唯一性。但效率很低,99%情况下不需要同步。
public class SingletonClass {
     private static SingletonClass instance = null;

     private SingletonClass(){
     }
     public synchronized static SingletonClass getInstance(){
         if(instance == null){
                instance = new SingletonClass();
         }
         return instance;
    }  
}
改为:
public class SingletonClass {
     private static SingletonClass instance = null;
     private SingletonClass() {
     }
     public static SingletonClass getInstance() {
         if (instance == null) {
              synchronized (SingletonClass.class) {
                 if (instance == null) {
                     instance = new SingletonClass();
                 }
              }
          }
          return instance;
     }
}
创建一个变量需要哪些步骤呢?一个是申请一块内存,调用构造方法进行初始化操作,另一个是分配一个指针指向这块内存。这两个操作谁在前谁在后呢?JVM规范并没有规定。那么就存在这么一种情况,JVM是先开辟出一块内存,然后把指针指向这块内存,最后调用构造方法进行初始化。
 
下面我们来考虑这么一种情况:线程A开始创建SingletonClass的实例,此时线程B调用了getInstance()方法,首先判断instance是否为null。按照我们上面所说的内存模型,A已经把instance指向了那块内存,只是还没有调用构造方法,因此B检测到instance不为null,于是直接把instance返回了——问题出现了,尽管instance不为null,但它并没有构造完成,就像一套房子已经给了你钥匙,但你并不能住进去,因为里面还没有收拾。此时,如果B在A将instance构造完成之前就是用了这个实例,程序就会出现错误了!

4、双重校验锁
     volatile写的内存语义如下:
     当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存。
     volatile读的内存语义如下:
     当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
public class SingletonClass {
     private volatile static SingletonClass instance = null;

     private SingletonClass() {
     }

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

5、静态内部类
public class SingletonClass {
     private static class SingletonClassInstance {
          private static final SingletonClass instance = new SingletonClass();
     }
     public static SingletonClass getInstance() {
          return SingletonClassInstance.instance;
     }
     private SingletonClass() {
     }
}
在这段代码中,因为SingletonClass没有static的属性,因此并不会被初始化。直到调用getInstance()的时候,会首先加载SingletonClassInstance类,这个类有一个static的SingletonClass实例,因此需要调用SingletonClass的构造方法,然后getInstance()将把这个内部类的instance返回给使用者。由于这个instance是static的,因此并不会构造多次。由于SingletonClassInstance是私有静态内部类,所以不会被其他类知道,同样,static语义也要求不会有多个实例存在。
并且,JSL规范定义,类的构造必须是原子性的,非并发的,因此不需要加同步块。同样,由于这个构造是并发的,所以getInstance()也并不需要加同步。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值