单例模式(多种写法)

什么是单例模式?

单例模式就是保证一个类只有一个对象的实例,实现这种功能的方式就叫单例模式。

如何实现单例模式

因为保证一个类只能有一个实例,不能多次实例化,不能允许用户new对象,所以需要将构造方法私有化,通过提供类的方法来让外部获取对象实例
单例模式主要存在两种方式实现:饿汉式懒汉式

饿汉式

饿汉式即不管你需不要这个对象,先创建好,等你用的时候直接拿来用就行。
饿汉式代码实现:

class Hungry { private static Hungry instance = new Hungry(); private Hungry(){ } public static Hungry getInstance(){ return instance; } public static void gethh(){ System.out.println("hhh"); } }

饿汉式是线程安全的。

懒汉式

懒汉式即需要用到该实例的时候再去创建对象。

class Lazy { private static Lazy instance; private Lazy(){ } public static Lazy getInstance(){ if(instance == null){ instance = new Lazy(); } return instance; } public static void gethh(){ System.out.println("hhh"); } }

该懒汉式代码在单线程下执行是没有问题的,但是在多线程下执行的话可能会因为并发问题导致实例化多次。因此可以对懒汉式加锁来控制类只允许被实例化一次。

懒汉式(加锁版)

class Lazy { private static Lazy instance; private Lazy(){ } public static Lazy getInstance(){ synchronized (Lazy.class){ if(instance == null){ instance = new Lazy(); } } return instance; } public static void gethh(){ System.out.println("hhh"); } }

通过加锁保证了单例模式的安全性。但是锁的粒度太大,会严重影响性能。因此可以采用DCL双重检锁机制。

懒汉式(DCL版)

class Lazy { private static Lazy instance; private Lazy(){ } public static Lazy getInstance(){ if(instance == null){ synchronized (Lazy.class){ if(instance == null){ instance = new Lazy(); } } } return instance; } public static void gethh(){ System.out.println("hhh"); } }

这样的话就是只有当对象未创建的时候才请求加锁,对象创建以后都不会再去获取锁加锁。这样就可以提高程序效率,并保证实例只有一个。但这段代码依然存在问题,因为JVM是存在指令重排的,所以在多线程下不一定是线程安全的。原因是当某一个线程第一次检测的时候,读取到instance不为空的话,instance的引用对象可能没有完成实例化。因为 instance = new Lazy();可以分为以下三步进行完成:

  • memory = allocate(); // 1、分配对象内存空间
  • instance(memory); // 2、初始化对象
  • instance = memory; // 3、设置instance指向刚刚分配的内存地址,此时instance != null
    因为2和3是不存在依赖关系的,所以可能会发生指令重排,在单线程下是完全没有问题的,但是多线程下就需要禁止重排。
  • memory = allocate(); // 1、分配对象内存空间
  • instance = memory; // 3、设置instance指向刚刚分配的内存地址,此时instance != null
  • instance(memory); // 2、初始化对象

当我们执行到重排后的步骤2,试图获取instance的时候,会得到null,因为对象的初始化还没有完成,而是在重排后的步骤3才完成,因此执行单例模式的代码时候,就会重新在创建一个instance实例。所以当一条线程访问instance不为null时,由于instance实例未必已初始化完成,这就造成了线程安全的问题。指令重排只会保证串行语义的执行一致性(单线程),但并不会关系多线程间的语义一致性
解决以上问题便可以使用volatile关键字,来禁止指令重排,保证单例模式的线程安全性。
最终代码为:

懒汉式(最终版)

class Lazy { private volatile static Lazy instance; private Lazy(){ } public static Lazy getInstance(){ if(instance == null){ synchronized (Lazy.class){ if(instance == null){ instance = new Lazy(); } } } return instance; } public static void gethh(){ System.out.println("hhh"); } }

__EOF__

本文作者Younger
本文链接https://www.cnblogs.com/youngerwb/p/16154386.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   YoungerWb  阅读(53)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示