对单例模式的饿汉式、懒汉式的思考

目录

  • [1 什么是单例模式?](#1 什么是单例模式?)
    • [1.1 什么是饿汉式?](#1.1 什么是饿汉式?)
    • [1.2 什么是懒汉式?](#1.2 什么是懒汉式?)
  • [2 我对饿汉式的思考](#2 我对饿汉式的思考)
  • [3 懒汉式](#3 懒汉式)
    • [3.1 解决懒汉式的线程安全问题](#3.1 解决懒汉式的线程安全问题)
      • [3.1.1 加锁:synchronized(synchronized修饰静态方法)](#3.1.1 加锁:synchronized(synchronized修饰静态方法))
      • [3.1.2 对"3.1.1"性能的改进](#3.1.2 对“3.1.1”性能的改进)

1 什么是单例模式?

  • 单例模式是指一个类在JVM中只有一个实例。

1.1 什么是饿汉式?

  • 在类加载的时候就创建好了实例。

1.2 什么是懒汉式?

  • 创建实例延迟到使用该实例前。

2 我对饿汉式的思考

  • 示例
java 复制代码
public class LearnSingleton {
    private static LearnSingleton instance = new LearnSingleton();

    private LearnSingleton() {

    }

    public static LearnSingleton getInstance() {
        return instance;
    }
}
  • 当我们调用LearnSingleton.getInstance()时,会触发LearnSingleton的加载。在类加载的准备阶段,创建好了静态变量表(此时instance对应的slot槽还为null),等到了初始化阶段,开始执行clinit方法,会创建LearnSingleton的示例(执行了new LearnSingleton()语句)。类加载后,执行getInstance()方法,将单例返回给用户。
    • 这么一看,上述写法不也是懒汉式吗?在使用时才创建实例啊。(虽然,这个实例也是在类加载时就创建好的)
  • 要想明白这到底是懒汉式,还是饿汉式,关键在于:
java 复制代码
public class LearnSingleton {
    private static LearnSingleton instance = new LearnSingleton();

    private LearnSingleton() {

    }

    public static LearnSingleton getInstance() {
        return instance;
    }

	public static int add(int x, int y) {
		return x + y;
	}
}
  • 当用户调用LearnSingleton.add(1, 2)时,在类加载过程中,就已经创建好了单例,但并未使用。因此,这不符合在需要时创建单例的定义。从这个例子,就能想明白了,这种写法是饿汉式。
  • 彻底的饿汉式:
java 复制代码
public class LearnSingleton {
    private static final LearnSingleton instance = new LearnSingleton();

    private LearnSingleton() {

    }

    public static LearnSingleton getInstance() {
        return instance;
    }
}
  • 只要这个类加载了,由于instance是常量,因此在类加载的准备阶段就创建好了单例。这是彻底的饿汉式。可谓"饿疯了"😃。
  • 饿汉式,是在类加载阶段完成实例的创建,由JVM保证了线程安全。

3 懒汉式

  • 在调用时才创建对象,示例:
java 复制代码
public class LearnLazySingleton {
    private static LearnLazySingleton instance;
    
    private LearnLazySingleton() {
        
    }
    
    // 存在线程安全问题。假设两个线程同时调用该方法,那么可能导致创建2个LearnLazySingleton对象。
    public static LearnLazySingleton getInstance() {
        if (instance == null) {
            instance = new LearnLazySingleton();
        }
        
        return instance;
    }
}

3.1 解决懒汉式的线程安全问题

3.1.1 加锁:synchronized(synchronized修饰静态方法)

java 复制代码
public class LearnLazySingleton {
    private static LearnLazySingleton instance;

    private LearnLazySingleton() {

    }

    public synchronized static LearnLazySingleton getInstance() {
        if (instance == null) {
            instance = new LearnLazySingleton();
        }

        return instance;
    }
	
	public synchronized static int add(int x, int y) {
		return x + y;
	}
	
	public static int sub(int x, int y) {
		return x - y;
	}
}
  • 当线程A先调用getInstance()方法的同时, 另一个线程B尝试访问add()方法,线程B会因为没有LearnLazySingleton的class对象的锁而等待。如果类中的其他方法不是synchronized的,它们就不会被锁定,即线程C调用sub方法()就不会等待。
    • 本质是因为,在调用同步方法前,只有获取锁,才能进入临界区。而如果不是临界区,那就不会受影响。

3.1.2 对"3.1.1"性能的改进

  • 上面的写法,有个非常难受的地方,例如,线程A已经调用getInstance()方法,创建好了单例。但线程B为了获取单例,也不得不调用getInstance()方法(唯一获取实例的入口),这时候就可能和线程C撞车,因为只要线程C调用add方法(),就可能让线程B获取单例时发生阻塞。
  • 改进:
java 复制代码
public class LearnLazySingleton {
    private static LearnLazySingleton instance;

    private LearnLazySingleton() {

    }

    public static LearnLazySingleton getInstance() {
        if (instance == null) {
            synchronized (LearnLazySingleton.class) {
            	// 这里一旦要在判断下,否则,线程A和线程B在这里排队进入临界区,会导致创建多个实例。
                if (instance == null) {
                    instance = new LearnLazySingleton();
                }
            }
        }

        return instance;
    }
    
	public synchronized static int add(int x, int y) {
		return x + y;
	}
}
  • 在创建单例前,仍然存在线程A和线程B争抢着执行"instance = new LearnLazySingleton();",也会影响线程C去调用add()方法。但一旦实例创建好后,instance不为null,线程们调用getInstance()方法就不会阻塞了。
相关推荐
Thinker QAQ29 分钟前
并发编程(十一):Shared Memory 与 Message Passing——关系与组合使用
java·python·go·并发编程·shared memory·message passing
齐 飞1 小时前
idea中git误操作撤回git reset --hard ‘HEAD@{1}‘
java·git·intellij-idea
你的保护色1 小时前
多AS互联BGP4综合实验设计
java
某不知名網友1 小时前
ROS2进阶:服务通信、自定义SRV接口、参数服务器与基于服务的参数通信详解
java·开发语言·microsoft
Wang's Blog2 小时前
Java 项目实战: 交友项目 - MongoDB 入门:体系结构和命令行操作
java·mongodb·交友
小溪学编程2 小时前
将 Bean 放入 Spring 容器中的方式有哪些?
java·后端·spring
java_nnnn2 小时前
LeetCode刷题滑动窗口(水果成篮+找到字符串中所有字母的异位词)Java
java·开发语言·leetcode
用户3721574261352 小时前
Java 读取、修改和清空 PDF 文档属性(元数据)
java
SL_staff2 小时前
从‘改一行代码等三天’到‘改完即生效’:实施工程师的逻辑引擎避坑手记
java·产品·全栈
小羊没烦恼!2 小时前
异步编程:线程概述及使用
java·开发语言·mysql·算法·c#