Java中单例模式的七种写法示例

作者:三分恶 时间:2021-07-16 07:57:22 

目录
  • 前言

  • 1、饿汉式(线程安全)⭐

  • 2、懒汉式(线程不安全)⭐

  • 3、懒汉式(加锁)

  • 4、懒汉式(双重校验锁)⭐

  • 5、单例模式(静态内部类)

  • 6、单例模式(CAS)

  • 7、单例模式(枚举)

  • 总结

前言

大家好,我是三乙己。考上大家一考:“单例模式的单例,怎样写的?”

“不就是构造方法私有化么?”

”对呀对呀!……单例模式有七种写法,你知道么?“

言归正传……

单例模式(Singleton Pattern)可以说是最简单的设计模式了。

用一个成语来形容单例模式——“天无二日,国无二主”。

什么意思呢?就是当前进程确保一个类全局只有一个实例。

那单例模式有什么好处呢?[1]

  • 单例模式在内存中只有一个实例,减少了内存开支

  • 单例模式只生成一个实例,所以减少了系统的性能开销

  • 单例模式可以避免对资源的多重占用

  • 单例模式可以在系统设置全局的访问点

那单例模式是银弹吗?它有没有什么缺点?

  • 单例模式一般没有接口,扩展很困难

  • 单例模式不利于测试

  • 单例模式与单一职责原则有冲突

那什么情况下要用单例模式呢?

  • 要求生成唯一序列号的环境

  • 在整个项目中需要一个共享访问点或共享数据

  • 创建一个对象需要消耗的资源过多

  • 需要定义大量的静态常量和静态方法(如工具类)的环境

接下来,进入今天的主题,我们来看看单例模式的七种写法!

1、饿汉式(线程安全)⭐


public class Singleton_1 {

private static Singleton_1 instance=new Singleton_1();

private Singleton_1() {
   }

public static Singleton_1 getInstance() {
       return instance;
   }

}

饿汉式,就像它的名字,饥不择食,定义的时候直接初始化。

因为instance是个静态变量,所以它会在类加载的时候完成实例化,不存在线程安全的问题。

这种方式不是懒加载,不管我们的程序会不会用到,它都会在程序启动之初进行初始化。

所以我们就有了下一种方式👇

2、懒汉式(线程不安全)⭐


public class Singleton_2 {

private static Singleton_2 instance;

private Singleton_2() {
   }

public static Singleton_2 getInstance() {
       if (instance == null) {
           instance = new Singleton_2();
       }
       return instance;
   }

}

懒汉式 是什么呢?只有用到的时候才会加载,这就实现了我们心心念的懒加载。

但是!

它又引入了新的问题?什么问题呢?线程安全问题。

Java中单例模式的七种写法示例

图片也很清楚,多线程的情况下,可能存在这样的问题:

一个线程判断instance==null,开始初始化对象;

还没来得及初始化对象时候,另一个线程访问,判断instance==null,也创建对象。

最后的结果,就是实例化了两个Singleton对象。

这不符合我们单例的要求啊?怎么办呢?

3、懒汉式(加锁)


public class Singleton_3 {

private static Singleton_3 instance;

private Singleton_3() {
   }

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

最直接的办法,直接上锁呗!

但是这种把锁直接方法上的办法,所有的访问都需要获取锁,导致了资源的浪费。

那怎么办呢?

4、懒汉式(双重校验锁)⭐


public class Singleton_4 {
   //volatile修饰,防止指令重排
   private static volatile Singleton_4 instance;

private Singleton_4() {
   }

public static Singleton_4 getInstance() {
       //第一重校验,检查实例是否存在
       if (instance == null) {
           //同步块
           synchronized (Singleton_4.class) {
               //第二重校验,检查实例是否存在,如果不存在才真正创建实例
               if (instance == null) {
                   instance = new Singleton_4();
               }
           }
       }
       return instance;
   }

}

这是比较推荐的一种,双重校验锁。

它的进步在哪里呢?

我们把synchronized加在了方法的内部,一般的访问是不加锁的,只有在instance==null的时候才加锁。

同时我们来看一下一些关键问题。

首先我们看第一个问题,为什么要双重校验?

大家想一下,如果不双重校验。

如果两个线程一起调用getInstance方法,并且都通过了第一次的判断instance==null,那么第一个线程获取了锁,然后实例化了instance,然后释放了锁,然后第二个线程得到了线程,然后马上也实例化了instance。这就不符合我们的单例要求了。

接着我们来看第二个问题,为什么要用volatile 修饰 instance?

我们可能知道答案是防止指令重排。

那这个重排指的是哪?指的是instance = new Singleton(),我们感觉是一步操作的实例化对象,实际上对于JVM指令,是分为三步的:

  • 分配内存空间

  • 初始化对象

  • 将对象指向刚分配的内存空间

有些编译器为为了性能优化,可能会把第二步和第三步进行重排序,顺序就成了:

  • 分配内存空间

  • 将对象指向刚分配的内存空间

  • 初始化对象

Java中单例模式的七种写法示例

所以呢,如果不使用volatile防止指令重排可能会发生什么情况呢?

Java中单例模式的七种写法示例

在这种情况下,T7时刻线程B对instance的访问,访问的是一个初始化未完成的对象。

所以需要在instance前加入关键字volatile。

  • 使用了volatile关键字后,可以保证有序性,指令重排序被禁止;

  • volatile还可以保证可见性,Java内存模型会确保所有线程看到的变量值是一致的。

5、单例模式(静态内部类)


public class Singleton_5 {

private Singleton_5() {
   }

private static class InnerSingleton {
       private static final Singleton_5 instance = new Singleton_5();
   }

public static Singleton_5 getInstance() {
       return InnerSingleton.instance;
   }
}

静态内部类是更进一步的写法,不仅能实现懒加载、线程安全,而且JVM还保持了指令优化的能力。

Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会加载静态内部类InnerSingleton类,从而完成Singleton的实例化。

类的静态属性只会在第一次加载类的时候初始化,同时类加载的过程又是线程互斥的,JVM帮助我们保证了线程安全。

6、单例模式(CAS)


public class Singleton_6 {

private static final AtomicReference<Singleton_6> INSTANCE = new AtomicReference<Singleton_6>();

private Singleton_6() {
   }

public static final Singleton_6 getInstance() {
       //等待
       while (true) {
           Singleton_6 instance = INSTANCE.get();
           if (null == instance) {
               INSTANCE.compareAndSet(null, new Singleton_6());
           }
           return INSTANCE.get();
       }
   }
}

这种CAS式的单例模式算是懒汉式直接加锁的一个变种,sychronized是一种悲观锁,而CAS是乐观锁,相比较,更轻量级。

当然,这种写法也比较罕见,CAS存在忙等的问题,可能会造成CPU资源的浪费。

7、单例模式(枚举)


public enum Singleton_7 {

//定义一个枚举,代表了Singleton的一个实例
   INSTANCE;
   public void anyMethod(){
       System.out.println("do any thing");
   }
}

调用方式:


   @Test
   void anyMethod() {
       Singleton_7.INSTANCE.anyMethod();
   }

《Effective Java》作者推荐的一种方式,非常简练。

但是这种写法解决了最主要的问题:线程安全、⾃由串⾏化、单⼀实例。

来源:https://blog.csdn.net/sinat_40770656/article/details/120263076

标签:单例模式,java,设计模式
0
投稿

猜你喜欢

  • spring boot实现过滤器和拦截器demo

    2023-08-24 07:15:01
  • 深入理解java final不可变性

    2023-02-11 20:17:27
  • Java面试题冲刺第二十三天--分布式

    2023-09-24 07:30:43
  • springboot 使用QQ邮箱发送邮件的操作方法

    2022-03-03 14:36:22
  • Mybatis-plus多数据源配置的两种方式总结

    2023-07-24 05:22:48
  • Android实现摇一摇功能

    2023-07-23 20:21:11
  • spring框架cacheAnnotation缓存注释声明解析

    2022-04-14 17:13:05
  • 如何优雅的处理Spring Boot异常信息详解

    2023-11-29 09:50:02
  • Java从同步容器到并发容器的操作过程

    2021-10-14 05:26:58
  • java转树形结构工具类详解

    2021-07-26 04:00:08
  • springboot 按月分表的实现方式

    2023-11-25 00:03:47
  • java 归并排序的实例详解

    2021-12-06 22:58:05
  • Android上传文件到服务端并显示进度条

    2023-06-23 07:48:33
  • Spring boot如何集成kaptcha并生成验证码

    2023-09-13 04:00:24
  • 大白话讲解C# 中的委托

    2023-02-03 02:06:41
  • maven打包时候修改包名称带上git版本号和打包时间方式

    2022-03-09 20:51:39
  • Java面试题冲刺第二十四天--并发编程

    2023-08-31 05:39:02
  • springboot实现FastJson解析json数据的方法

    2023-11-27 22:12:48
  • Java判断两个日期相差天数的方法

    2021-11-29 05:55:07
  • Java实现树形结构的示例代码

    2023-07-30 01:05:19
  • asp之家 软件编程 m.aspxhome.com