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

目录

  • [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()方法就不会阻塞了。
相关推荐
带刺的坐椅23 分钟前
Solon v3.4.7, v3.5.6, v3.6.1 发布(国产优秀应用开发框架)
java·spring·solon
四谎真好看2 小时前
Java 黑马程序员学习笔记(进阶篇18)
java·笔记·学习·学习笔记
桦说编程2 小时前
深入解析CompletableFuture源码实现(2)———双源输入
java·后端·源码
java_t_t2 小时前
ZIP工具类
java·zip
lang201509282 小时前
Spring Boot优雅关闭全解析
java·spring boot·后端
pengzhuofan3 小时前
第10章 Maven
java·maven
百锦再4 小时前
Vue Scoped样式混淆问题详解与解决方案
java·前端·javascript·数据库·vue.js·学习·.net
刘一说4 小时前
Spring Boot 启动慢?启动过程深度解析与优化策略
java·spring boot·后端
壹佰大多4 小时前
【spring如何扫描一个路径下被注解修饰的类】
java·后端·spring
百锦再4 小时前
对前后端分离与前后端不分离(通常指服务端渲染)的架构进行全方位的对比分析
java·开发语言·python·架构·eclipse·php·maven