Java并发编程中的设计模式解析(二)一个单例的七种写法

简介: Java单例模式是最常见的设计模式之一,广泛应用于各种框架、中间件和应用开发中。单例模式实现起来比较简单,基本是每个Java工程师都能信手拈来的,本文将结合多线程、类的加载等知识,系统地介绍一下单例模式的演变,并体现在7种不同的单例设计中。

Java单例模式是最常见的设计模式之一,广泛应用于各种框架、中间件和应用开发中。单例模式实现起来比较简单,基本是每个Java工程师都能信手拈来的,本文将结合多线程、类的加载等知识,系统地介绍一下单例模式的演变,并体现在7种不同的单例设计中。说到这个,非常像孔乙己里那个“回字有四种写法”的梗,不过与封建迂腐文人不同的是,从简单的单例设计变化,可以看到一个需求演变的过程,看到一个方法不断完善的过程。

传送门:Java并发编程中的设计模式解析(一)

1. 饿汉式

最简单的单例设计,优点是线程安全,但是因为类加载即初始化实例,加入实例变量比较多的话,会占用较多的内存。

 1 //不允许被继承
 2 public final class SingletonStarve {
 3     //实例变量, 由于单例对象是静态的, 在类的加载阶段, 就会初始化实例变量
 4     @SuppressWarnings("unused")
 5     private byte[] data = new byte[1024];
 6     //定义静态实例对象的时候直接初始化
 7     private static SingletonStarve instance = new SingletonStarve();
 8     //私有化构造函数, 不允许直接new对象
 9     private SingletonStarve() {}
10     //提供公共的方法获取实例对象
11     public static SingletonStarve getInstance() {
12         return instance;
13     }
14 }

2. 懒汉式

实现了单例设计的懒加载,节省了前期内存空间的占用,但是在多线程环境下可能会导致多对象的产生,破坏实例唯一性。

 1 //不允许被继承
 2 public final class LazySingleton {
 3     //实例变量, 由于单例对象是静态的, 在类的加载阶段, 就会初始化实例变量
 4     @SuppressWarnings("unused")
 5     private byte[] data = new byte[1024];
 6     //定义静态实例对象, 不直接初始化
 7     private static LazySingleton instance = null;
 8     //私有化构造函数, 不允许直接new对象
 9     private LazySingleton() {}
10     //提供公共的方法获取实例对象
11     public static LazySingleton getInstance() {
12         if(null == instance) {
13             instance = new LazySingleton();
14         }
15         return instance;
16     }
17 }

3. 懒汉式+同步锁

通过使用synchronized关键字使getInstance方法变为同步方法,从而确保线程安全,但带来了一定的性能问题。

 1 //不允许被继承
 2 public final class SyncLazySingleton {
 3     //实例变量, 由于单例对象是静态的, 在类的加载阶段, 就会初始化实例变量
 4     @SuppressWarnings("unused")
 5     private byte[] data = new byte[1024];
 6     //定义静态实例对象, 不直接初始化
 7     private static SyncLazySingleton instance = null;
 8     //私有化构造函数, 不允许直接new对象
 9     private SyncLazySingleton() {}
10     //提供公共的方法获取实例对象, 通过synchronized修饰为同步方法
11     public static synchronized SyncLazySingleton getInstance() {
12         if(null == instance) {
13             instance = new SyncLazySingleton();
14         }
15         return instance;
16     }
17 }

4. Double-Check

推荐使用:Double-Check单例模式,通过两次非空判断,并且对第二次判断加锁,确保了多线程下的单例设计安全,同时保证了性能。

注意:Double-check有可能因为JVM指令重排的原因,导致空指针异常;使用volatile修饰对象引用,可以确保其可见性,避免异常

 1 //不允许被继承
 2 public final class VolatileDoubleCheckSingleton {
 3     //实例变量, 由于单例对象是静态的, 在类的加载阶段, 就会初始化实例变量
 4     @SuppressWarnings("unused")
 5     private byte[] data = new byte[1024];
 6     //定义静态实例对象, 不直接初始化
 7     //通过volatile, 避免指令重排序导致的空指针异常
 8     private static volatile VolatileDoubleCheckSingleton instance = null;
 9     Connection conn;
10     Socket socket;
11     //私有化构造函数, 不允许直接new对象
12     //由于指令重排序, 实例化顺序可能重排, 从而导致空指针,使用volatile关键字修饰单例解决
13     private VolatileDoubleCheckSingleton() {
14         //this.conn;
15         //this.socket;
16     }
17     //提供公共的方法获取实例对象
18     public static VolatileDoubleCheckSingleton getInstance() {
19      20         if(null == instance) {
21             synchronized(VolatileDoubleCheckSingleton.class) {
22                 if(null == instance) {//以下赋值因为不是原子性的,如果不使用volatile使instance在多个线程中可见,将可能导致空指针
23                     instance = new VolatileDoubleCheckSingleton();                    
24                 }
25             }
26         }
27         return instance;
28     }
29 }

5. 静态内部类

推荐使用:通过使用静态内部类,巧妙地避免了线程不安全,并且节省了前期内存空间,编码非常简洁。

 1 //不允许被继承
 2 public final class HolderSingleton {
 3     //实例变量
 4     @SuppressWarnings("unused")
 5     private byte[] data = new byte[1024];
 6     //私有化构造器
 7     private HolderSingleton() {}
 8     //定义静态内部类Holder, 及内部实例成员, 并直接初始化
 9     private static class Holder{
10         private static HolderSingleton instance = new HolderSingleton();
11     }
12     //通过Holder.instance获得单例
13     public static HolderSingleton getInstance() {
14         return Holder.instance;
15     }
16 }

 

6. 枚举类

《Effective Java》中推荐的单例设计模式,缺点是饿汉式,并且对编码能力要求较高。

 1 //枚举本身是final的, 不允许被继承
 2 public enum EnumSingleton {
 3     INSTANCE;
 4     //实例变量
 5     @SuppressWarnings("unused")
 6     private byte[] data = new byte[1024];
 7     
 8     EnumSingleton() {
 9         System.out.println("INSTANCE will be initialized immediately");
10     }
11     public static void method() {
12         //调用该方法会主动使用EnumSingleton, INSTANCE将会实例化
13     }
14     public static EnumSingleton getInstance() {
15         return INSTANCE;
16     }
17 }

7. 内部枚举类

 1 /*
 2  * 使用枚举类作为内部类实现懒加载
 3  */
 4 public final class LazyEnumSingleton {
 5     private LazyEnumSingleton(){}
 6     private enum EnumHolder{
 7         INSTANCE;
 8         private LazyEnumSingleton instance;
 9         EnumHolder(){
10             this.instance = new LazyEnumSingleton();
11         }
12         private LazyEnumSingleton getLazyEnumSingleton() {
13             return instance;
14         }
15     }
16     public static LazyEnumSingleton getInstance() {
17         return EnumHolder.INSTANCE.getLazyEnumSingleton();
18     }
19 }
目录
相关文章
|
设计模式 SQL Java
【再谈设计模式】解释器模式~语法的解析执行者
解释器模式定义了一种语言的语法表示,并定义一个解释器来解释该语言中的句子。它使用类来表示每个语法规则,并且通过递归调用这些类的方法来解释表达式。本质上,它将一个复杂的表达式分解为一系列简单的部分,然后按照特定的语法规则进行解析和执行。
350 8
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
1245 2
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
1482 1
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
设计模式 缓存 安全
「全网最细 + 实战源码案例」设计模式——单例设计模式
单例模式是一种创建型设计模式,确保一个类在整个程序运行期间只有一个实例,并提供一个全局访问点来获取该实例。它常用于控制共享资源的访问,如数据库连接、配置管理等。实现方式包括饿汉式(类加载时初始化)、懒汉式(延迟加载)、双重检查锁、静态内部类和枚举单例等。其中,枚举单例最简单且安全,能有效防止反射和序列化破坏。
326 7
|
11月前
|
机器学习/深度学习 消息中间件 存储
【高薪程序员必看】万字长文拆解Java并发编程!(9-2):并发工具-线程池
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的强力并发工具-线程池,废话不多说让我们直接开始。
407 0
|
安全 Java 调度
解锁Java并发编程高阶技能:深入剖析无锁CAS机制、揭秘魔法类Unsafe、精通原子包Atomic,打造高效并发应用
【8月更文挑战第4天】在Java并发编程中,无锁编程以高性能和低延迟应对高并发挑战。核心在于无锁CAS(Compare-And-Swap)机制,它基于硬件支持,确保原子性更新;Unsafe类提供底层内存操作,实现CAS;原子包java.util.concurrent.atomic封装了CAS操作,简化并发编程。通过`AtomicInteger`示例,展现了线程安全的自增操作,突显了这些技术在构建高效并发程序中的关键作用。
265 1
|
11月前
|
缓存 安全 Java
【高薪程序员必看】万字长文拆解Java并发编程!(3-1):并发共享问题的解决与分析
活锁:多个线程相互影响对方退出同步代码块的条件而导致线程一直运行的情况。例如,线程1的退出条件是count=5,而线程2和线程3在其代码块中不断地是count进行自增自减的操作,导致线程1永远运行。内存一致性问题:由于JIT即时编译器对缓存的优化和指令重排等造成的内存可见性和有序性问题,可以通过synchronized,volatile,并发集合类等机制来解决。这里的线程安全是指,多个线程调用它们同一个实例的方法时,是线程安全的,但仅仅能保证当前调用的方法是线程安全的,不同方法之间是线程不安全的。
198 0
|
11月前
|
Java 程序员
【高薪程序员必看】万字长文拆解Java并发编程!(3-2):并发共享问题的解决与分析
wait方法和notify方法都是Object类的方法:让当前获取锁的线程进入waiting状态,并进入waitlist队列:让当前获取锁的线程进入waiting状态,并进入waitlist队列,等待n秒后自动唤醒:在waitlist队列中挑一个线程唤醒:唤醒所有在waitlist队列中的线程它们都是之间协作的手段,只有拥有对象锁的线程才能调用这些方法,否则会出现IllegalMonitorStateException异常park方法和unpark方法是LockSupport类中的方法。
199 0

推荐镜像

更多
  • DNS
  • 下一篇
    开通oss服务