【编程进阶知识】Java单例模式深度解析:饿汉式与懒汉式实现技巧

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 本文深入解析了Java单例模式中的饿汉式和懒汉式实现方法,包括它们的特点、实现代码和适用场景。通过静态常量、枚举类、静态代码块等方式实现饿汉式,通过非线程安全、同步方法、同步代码块、双重检查锁定和静态内部类等方式实现懒汉式。文章还对比了各种实现方式的优缺点,帮助读者在实际项目中做出更好的设计决策。

Java单例模式深度解析:饿汉式与懒汉式实现技巧

摘要: 在Java编程中,单例模式是一种常用的设计模式,用于确保一个类只有一个实例,并提供一个全局访问点。本文深入探讨了饿汉式和懒汉式单例模式的实现方法,包括它们的特点、实现代码和适用场景。通过阅读本文,你将能够理解单例模式的核心要点,并掌握如何在实际项目中正确应用这两种单例模式。

关键词: Java单例模式,饿汉式,懒汉式,设计模式,线程安全

1. 单例模式概述

单例模式是一种创建型设计模式,它确保某个类只有一个实例,并提供一个全局访问点。这种模式在Java中广泛应用,如Runtime类。

核心要点:

  1. 唯一实例: 类的实例必须是唯一的。
  2. 内部创建: 构造器必须私有化,防止外部创建实例。
  3. 全局访问: 提供一个公共的静态方法获取实例。
  4. 创建时机: 分为饿汉式和懒汉式。

2. 饿汉式单例模式

饿汉式单例模式在类加载时就创建实例,避免了线程同步问题。

2.1 实现方式

  1. 静态常量实例化
  2. 枚举类
  3. 静态代码块

2.2 代码示例

//1、静态常量直接实例化(代表jvm运行环境的Runtime类生成单例,采用的就是这种实现方式,简洁直观);
class HungrySigleton1 {
   
    private HungrySigleton1(){
   
    }
    private final static HungrySigleton1 INSTANCE = new HungrySigleton1();
    public static HungrySigleton1 getInstance(){
   
        return INSTANCE;
    }
}

//2、枚举类(最简洁);
enum HungrySigleton3{
   
    INSTANCE;
    private HungrySigleton3() {
   
    }
}

//3、静态代码块(适合复杂的场景,如从配置文件读取所需参数)。
class HungrySigleton2 {
   
    private HungrySigleton2(String info){
   System.out.println(info);}
    private  static HungrySigleton2 INSTANCE =null;
    static {
   
        Properties prop = new Properties();
        InputStream inStream = HungrySigleton2.class.getClassLoader().getResourceAsStream("project.properties");
        try {
   prop.load(inStream);} catch (IOException e) {
   e.printStackTrace();}
        String info =(String) prop.get("info");
        //扩展:读取配置文件的几种方式 1、Properties继承自Hashtable 2、ResourceBundle 3、Spring种利用ApplicationContext加载xml文件
         INSTANCE = new HungrySigleton2(info);
    }
    public static HungrySigleton2 getInstance(){
   
        return INSTANCE;
    }
}
public class Hungry{
   
    public static void main(String[] args) {
   
        HungrySigleton1 ins1 = HungrySigleton1.getInstance();
        HungrySigleton2 ins2 = HungrySigleton2.getInstance();
        HungrySigleton3 ins3 = HungrySigleton3.INSTANCE;
    }
}

3. 懒汉式单例模式

懒汉式单例模式在第一次使用时才创建实例,可能需要处理线程同步问题。

3.1 实现方式

  1. 非线程安全版本
  2. 同步方法实现
  3. 同步代码块实现
  4. 双重检查锁定(DCL)
  5. 静态内部类

3.2 代码示例

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

//2、懒汉式版本2(同步方法实现线程安全,但效率很低);
class LazySigleton2 {
   
    private LazySigleton2(){
   
    }
    private  static LazySigleton2 INSTANCE =null;
    public synchronized static LazySigleton2 getInstance(){
   
        if(null == INSTANCE){
   
            try {
   Thread.sleep(100);} catch (InterruptedException e) {
   e.printStackTrace();}
            INSTANCE = new LazySigleton2();
        }
        return INSTANCE;
    }
}

//3、懒汉式版本2(同步代码块提供了一定效率,但存在线程安全问题);
class LazySigleton3 {
   
    private LazySigleton3(){
   
    }
    private  static LazySigleton3 INSTANCE =null;
    public  static LazySigleton3 getInstance(){
   
        if(null == INSTANCE){
   
            try {
   Thread.sleep(100);} catch (InterruptedException e) {
   e.printStackTrace();}
            synchronized(INSTANCE){
   
                INSTANCE = new LazySigleton3();
            }
        }
        return INSTANCE;
    }
}

//4、懒汉式版本2(DCL(Double Check Lock),线程安全效率较高,但由于synchronized无法避免jvm优化时可能出现的乱序执行,可能会遇到拿到的实例对象尚未被正确初始化的问题,这时可以利用volatile来解决);
class LazySigleton4 {
   
    private LazySigleton4(){
   
    }
    private  static volatile LazySigleton4 INSTANCE =null;
    public  static LazySigleton4 getInstance(){
   
        if(null == INSTANCE){
   
            try {
   Thread.sleep(100);} catch (InterruptedException e) {
   e.printStackTrace();}
            synchronized(INSTANCE){
   
                if(null == INSTANCE){
   
                    INSTANCE = new LazySigleton4();
                }
            }
        }
        return INSTANCE;
    }
}
//5、静态内部类(内部类被加载和初始化时才会创建对象,即调用时)
class LazySigleton5 {
   
    private LazySigleton5(){
   
    }
    private static class Inner{
   
         private final static LazySigleton5 INSTANCE = new LazySigleton5();
    }
    public static LazySigleton5 getInstance(){
   
        return Inner.INSTANCE;
    }
}

public class Lazy {
   
    public static void main(String[] args) throws InterruptedException, ExecutionException {
   
        ExecutorService exec = Executors.newFixedThreadPool(2);
        Callable task = new Callable(){
   
            @Override
            public Object call() {
   
                LazySigleton1 instance = LazySigleton1.getInstance();
                System.out.println(Thread.currentThread().getName()+instance);
                return instance;
            }
        };
        Future f1 = exec.submit(task);
        Future f2 = exec.submit(task);
        LazySigleton1 l1 = (LazySigleton1) f1.get(),l2 = (LazySigleton1) f2.get();
        System.out.println("l1:" + l1);
        System.out.println("l2:" + l2);
        System.out.println(l1==l2);
        exec.shutdown();
    }
}

4. 优缺点对比

实现方式 优点 缺点
饿汉式 - 线程安全
- 简单直观
- 类加载时就创建实例,可能导致资源浪费
懒汉式非线程安全 - 延迟实例化
- 节省资源
- 非线程安全
懒汉式同步方法 - 线程安全 - 效率低,每次访问都要同步
懒汉式同步代码块 - 线程安全
- 效率较高
- 复杂的线程同步问题
双重检查锁定 - 线程安全
- 高效
- 需要处理指令重排问题
静态内部类 - 线程安全
- 延迟加载
- 代码复杂度较高

5. 流程图

graph TD
    A[开始] --> B[检查实例]
    B -->|不存在| C[创建实例]
    B -->|存在| D[返回实例]
    C --> D

6. 结语

单例模式是Java中一种非常重要的设计模式,适用于需要严格控制实例数量的场景。通过本文的介绍,你应该对饿汉式和懒汉式单例模式有了更深入的理解。希望这些信息能帮助你在实际开发中做出更好的设计决策。

思维导图:

graph LR
    A[单例模式] --> B[饿汉式]
    A --> C[懒汉式]
    B --> D[静态常量]
    B --> E[枚举类]
    B --> F[静态代码块]
    C --> G[非线程安全]
    C --> H[同步方法]
    C --> I[同步代码块]
    C --> J[双重检查锁定]
    C --> K[静态内部类]

Excel表格:

实现方式 优点 缺点
饿汉式 - 线程安全
- 简单直观
- 类加载时就创建实例,可能导致资源浪费
懒汉式非线程安全 - 延迟实例化
- 节省资源
- 非线程安全
懒汉式同步方法 - 线程安全 - 效率低,每次访问都要同步
懒汉式同步代码块 - 线程安全
- 效率较高
- 复杂的线程同步问题
双重检查锁定 - 线程安全
- 高效
- 需要处理指令重排问题
静态内部类 - 线程安全
- 延迟加载
- 代码复杂度较高

鼓励话语: 掌握单例模式,就像是掌握了控制Java世界的一把钥匙。如果你有更多的见解或者遇到了难题,不妨在评论区分享,让我们一起探讨,共同进步!

目录
相关文章
|
1天前
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
10 3
|
1天前
|
存储 算法 Java
Java Set深度解析:为何它能成为“无重复”的代名词?
Java Set深度解析:为何它能成为“无重复”的代名词?本文详解Set接口及其主要实现类(HashSet、TreeSet、LinkedHashSet)的“无重复”特性,探讨其内部数据结构和算法实现,并通过示例代码展示最佳实践。
7 3
|
4天前
|
安全 Java UED
Java中的多线程编程:从基础到实践
本文深入探讨了Java中的多线程编程,包括线程的创建、生命周期管理以及同步机制。通过实例展示了如何使用Thread类和Runnable接口来创建线程,讨论了线程安全问题及解决策略,如使用synchronized关键字和ReentrantLock类。文章还涵盖了线程间通信的方式,包括wait()、notify()和notifyAll()方法,以及如何避免死锁。此外,还介绍了高级并发工具如CountDownLatch和CyclicBarrier的使用方法。通过综合运用这些技术,可以有效提高多线程程序的性能和可靠性。
|
4天前
|
缓存 Java UED
Java中的多线程编程:从基础到实践
【10月更文挑战第13天】 Java作为一门跨平台的编程语言,其强大的多线程能力一直是其核心优势之一。本文将从最基础的概念讲起,逐步深入探讨Java多线程的实现方式及其应用场景,通过实例讲解帮助读者更好地理解和应用这一技术。
22 3
|
4天前
|
Java 开发者
在Java编程中,正确的命名规范不仅能提升代码的可读性和可维护性,还能有效避免命名冲突。
【10月更文挑战第13天】在Java编程中,正确的命名规范不仅能提升代码的可读性和可维护性,还能有效避免命名冲突。本文将带你深入了解Java命名规则,包括标识符的基本规则、变量和方法的命名方式、常量的命名习惯以及如何避免关键字冲突,通过实例解析,助你写出更规范、优雅的代码。
26 3
|
4天前
|
Java 程序员
在Java编程中,关键字不仅是简单的词汇,更是赋予代码强大功能的“魔法咒语”。
【10月更文挑战第13天】在Java编程中,关键字不仅是简单的词汇,更是赋予代码强大功能的“魔法咒语”。本文介绍了Java关键字的基本概念及其重要性,并通过定义类和对象、控制流程、访问修饰符等示例,展示了关键字的实际应用。掌握这些关键字,是成为优秀Java程序员的基础。
12 3
|
4天前
|
Java 程序员 编译器
在Java编程中,保留字(如class、int、for等)是具有特定语法意义的预定义词汇,被语言本身占用,不能用作变量名、方法名或类名。
在Java编程中,保留字(如class、int、for等)是具有特定语法意义的预定义词汇,被语言本身占用,不能用作变量名、方法名或类名。本文通过示例详细解析了保留字的定义、作用及与自定义标识符的区别,帮助开发者避免因误用保留字而导致的编译错误,确保代码的正确性和可读性。
16 3
|
4天前
|
算法 Java
在Java编程中,关键字和保留字是基础且重要的组成部分,正确理解和使用它们
【10月更文挑战第13天】在Java编程中,关键字和保留字是基础且重要的组成部分。正确理解和使用它们,如class、int、for、while等,不仅能够避免语法错误,还能提升代码的可读性和执行效率。本指南将通过解答常见问题,帮助你掌握Java关键字的正确使用方法,以及如何避免误用保留字,使你的代码更加高效流畅。
20 3
|
3天前
|
存储 安全 Java
了解final关键字在Java并发编程领域的作用吗?
在Java并发编程中,`final`关键字不仅用于修饰变量、方法和类,还在多线程环境中确保对象状态的可见性和不变性。本文深入探讨了`final`关键字的作用,特别是其在final域重排序规则中的应用,以及如何防止对象的“部分创建”问题,确保线程安全。通过具体示例,文章详细解析了final域的写入和读取操作的重排序规则,以及这些规则在不同处理器上的实现差异。
了解final关键字在Java并发编程领域的作用吗?
|
4天前
|
存储 监控 算法
Java中的内存管理与垃圾回收机制解析
本文深入探讨了Java编程语言中的内存管理方式,特别是垃圾回收机制。我们将了解Java的自动内存管理是如何工作的,它如何帮助开发者避免常见的内存泄漏问题。通过分析不同垃圾回收算法(如标记-清除、复制和标记-整理)以及JVM如何选择合适的垃圾回收策略,本文旨在帮助Java开发者更好地理解和优化应用程序的性能。

推荐镜像

更多