JAVA多线程番外篇 1、单例模式与Volatile

本文详细介绍了Java中的单例模式,包括饿汉式和懒汉式的实现,探讨了它们的优缺点和线程安全性。特别强调了懒汉式中volatile关键字的重要性,以保证在多线程环境下的正确性。此外,还提到了静态内部类和枚举实现单例的高效方式。文章最后总结了单例模式在并发编程中的应用和注意事项。

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

1. 单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式的 UML 图

1.1 饿汉实现

这种方式比较简单,但在高性能程序上很少会这样使用。

  • 优点:没有加锁,执行效率会提高。
  • 缺点:类加载时就初始化,浪费内存。
    它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,导致资源浪费、延迟了启动程序时间。
public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }  
}

1.2 懒汉实现

  • 不安全的方式

    • 由于多线程环境,该方式是一个非常不安全的方法,会导致创建多个对象。
  public class Singleton {  
      private static Singleton instance;  
      private Singleton (){}  
    
      public static Singleton getInstance() {  
          if (instance == null) {  
              instance = new Singleton();  
          }  
          return instance;  
      }  
  }
  • 效率很低,每个线程在获取单例的时候都需要竞争锁,导致资源浪费和效率低下。
    public class Singleton {  
        private static Singleton instance;  
        private Singleton (){}  
        public static synchronized Singleton getInstance() {  
            if (instance == null) {  
                instance = new Singleton();  
            }  
            return instance;  
        }  
    }
  • 双重检查与Volatile。
这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
**问题来了,为什么需要volatile来修饰单例对象**
这是因为操作系统和java虚拟机会对程序进行优化,导致原本顺序执行的代码,在操作系统和虚拟机认为是安全的情况下,进行优化,导致代码乱序执行。在通常情况下,这种“优化”对于程序是没有影响的,volatile可以保证操作系统对于singleton对象的读写过程不允许乱序执行。其核心原理是通过在volatile变量读写时使用内存屏障。
public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
            if (singleton == null) {  
                singleton = new Singleton();  
            }  
        }  
    }  
    return singleton;  
    }  
}

举例来说,有A、B两个线程,A在执行到这部分时,内存已经创建好了对象,但是singleton执行内存中的对象还没有完全实例化。当A释放锁后,B线程判断singleton是否为null?不为null,于是B线程直接返回了singleton对象,此时该对象还没有完全实例化。

synchronized (Singleton.class) {  
            if (singleton == null) {  
                singleton = new Singleton(); 
  • 静态内部类
    Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。
    因此,这种方式相比DCL方式显得更合理。
public class Singleton {  
    private static class SingletonHolder {  
    private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE;  
    }  
}
  • 枚举
    这种方式应用的很少,但实际上是最佳实践方式。更简洁,自动支持序列化机制,绝对防止多次实例化。不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。
public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

总结

由于编译器优化等原因,我们的程序代码会存在乱序执行,以至于对象在发布时是半初始化的,为保证懒汉模式中数据的导出是安全的,需要增加volatile修饰。

多线程系列在github上有一个开源项目,主要是本系列博客的实验代码。

https://github.com/forestnlp/concurrentlab

如果您对软件开发、机器学习、深度学习有兴趣请关注本博客,将持续推出Java、软件架构、深度学习相关专栏。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悟空学编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值