详解JAVA面向对象的设计模式 (一)、单例模式

本系列,记录了我深刻学习设计模式的过程。也算是JAVA进阶学习的一个重要知识点吧。

与设计相关的代码会贴出,但是基础功能的代码会快速带过。有任何错误的地方,都欢迎读者评论指正,感谢。冲冲冲!

单例模式 Singleton

应用场景

只需要一个实例存在的场景

  • 比如各种Manager 类

  • 比如各种Factory 类

实现方式

总得来说,一共有8种单例的实现方式。其中只有2种是完美解决单例问题的。

其他6种都有他的缺点。下面会通过先讲述6个实现并解决他们的缺点的方式,一个个来讲实现。

1、2、饿汉式

先来看看它的实现

1 public class Mrg01 { 2     // static类型保证在classLoader装载这个类时,就实例化了这个对象 3     private static final Mgr01 INSTANCE = new Mgr01(); 4     /** 5     //这是第二种写法 6     //这样的写法和上面单句的写法是一个意思,只是初始化写在静态语句块里 7     private static final Mgr01 INSTANCE = null; 8     static { 9         this.INSTANCE = new Mgr01();10     }11     **/12     13     // 私有构造方法避免其他类new出该对象14     private Mgr01() {}15     // 想要拿到这个类的实例,就得用该类静态公共方法获取16     public static Mgr01 getInstance() {return INSTANCE;}17     // 这个类的任意方法18     public void m() {System.out.println("do method");}19 }
  • 优点:

    很简单清晰的实现,在日常的开发中也推荐这种方式来实现单例模式,因为这种方式简单实用。

    这种实现是线程安全的,因为JVM只会初始化一次INSTANCE对象。

  • 缺点:

    唯一的缺点就是不管这个类是否被用到,它都会被完成实例化。

3、懒汉式

懒汉式相比上面的饿汉式,它主要是弥补饿汉式的缺点,于是就有下面代码

1 public class Mgr03 { 2     private static Mgr03 INSTANCE; 3     private Mgr03() {} 4     // 当调用该静态方法时,根据对象是否为空,决定是否new一个对象出来,最后返回该对象 5     public static Mgr03 getInstance() { 6         if (INSTANCE == null) { 7             INSTANCE = new Mgr03(); 8         } 9         return INSTANCE;10     }11     12     public void m() {System.out.println("do method");}13 }

看似这样的实现解决了饿汉式的提前初始化问题,但是却带来了线程不安全问题

原因很简单,当首个线程Thread1进入静态方法时,此时INSTANCE==null,程序会执行new Mgr03(),但是此时线程Thread2也进入静态方法,但Thread1还未完成INSTANCE初始化,那么Thread2也会去new Mgr03()。这样就在首次使用时无法保证该类的单例存在。

4、懒汉式 -- 优化

为了解决懒汉式的线程不安全问题,我们尝试用synchronized解决它

1 public class Mgr04 { 2     private static Mgr04 INSTANCE; 3     private Mgr04() {} 4     // 此方法加上synchronized修饰以后,该代码块变成同步代码块(加锁) 5     // 如此一来就可以保证线程顺序执行该方法,也就解决了线程不安全问题 6     public static synchronized Mgr04 getInstance() { 7         if (INSTANCE == null) { 8             INSTANCE = new Mgr04(); 9         }10         return INSTANCE;11     }12     public void m() {System.out.println("do method");}13 }

看似又解决了问题,但是由于同步锁的存在,使得这个静态方法的效率急剧下降。因为每个线程必须在得到锁以后才能获取到对象,得不到锁就只能阻塞线程,能不慢吗(只是相比下很慢)。

5、懒汉式 -- 再优化

1 public class Mgr05 { 2     private static Mgr05 INSTANCE; 3     private Mgr05() {} 4     // 这一次,我把同步代码块放在if (INSTANCE==null) 后面 5     // 代码行数标出来方便下面理解 6     public static Mgr05 getInstance() {         //4 7         if (INSTANCE == null) {                 //5 8             synchronized (Mgr05.class) {        //6 9                 INSTANCE = new Mgr05();         //7    10             }                                   //811         }                                       //912          return INSTANCE;                       //1013     }                                           //1114     15     public void m() {System.out.println("do method");}16 }

这样的写法,因为先检查是否已经实例化INSTANCE,再加锁在初始化代码块上,又又看似解决了上面的效率问题,其实又带来了新的问题。

首先首次Thread1,Thread2 几乎同时进入到getInstance方法中,两个线程执行到第5行,INSTANCE为空,Thread1先拿到第6行代码的锁,Thread2等待锁释放,Thread1执行第7行,然后释放锁,之后Thread2获得锁,也执行第7行。这样INSTANCE就被初始化了2次,仍然线程不安全。

6、双重检查 double-check locking (DCL单例)

如果检查一次不行,那就再检查一次如何

1 public class Mgr06 { 2     private static volatile Mgr06 INSTANCE; 3     // private static Mgr06 INSTANCE; 4     // volatile 关键字可以保证多个线程初始化时,CPU指令能顺序执行 5     private Mgr06() {} 6     public static Mgr06 getInstance() { 7         // 双重检查 8         if (INSTANCE == null) { 9             synchronized (Mgr06.class) {10                 if (INSTANCE == null) {11                     INSTANCE = new Mgr06();12                 }
13             }14         }15         return INSTANCE;16     }17     18     public void m() {System.out.println("do method");}19 }

双重检查下,这样的单例模式就没有上面的各种问题了。

但是!关于DCL单例需不需要加volatile关键字的问题,这里还需要额外说明一下。

由于JVM是没法保证CPU指令顺序执行的(允许乱序执行),不加上volatile关键字就会可能出现意外。

而volatile 关键字可以保证多个线程初始化时,CPU指令能顺序执行

需要知道的是instance = new Mgr05();这句代码并不是一个原子操作,他的操作大体上可以被拆分为三步

1.分配内存空间

2.实例化对象instance

3.把instance引用指向已分配的内存空间,此时instance有了内存地址,不再为null了

java是允许对指令进行重排序, 那么以上的三步的执行顺序就有可能是1-3-2. 在这种情况下, 如果线程A执行完1-3之后被阻塞了, 而恰好此时线程B进来了 此时的instance已经不为空了所以线程B判断完INSTANCE == null 结果为 false 以后就直接返回了这个还没有实例化好的instance, 所以在调用其后续的实例方法时就会得不到预期的结果

具体可以参考该文章:https://blog.csdn.net/ACreazyCoder/article/details/80982578

虽然这算是个完美的解决方法。但是代码上还是不够简洁。

7、静态内部类

先来看代码

public class Mgr07() {private Mgr07() {}// 加载外部类时不会加载内部类,这样可以实现懒加载// 同时JVM也保证了静态对象的单例性质private static class Mgr07Holder {private final static Mgr07 INSTANCE = new Mgr07();
    }    public static getInstance() {return Mgr07Holder.INSTANCE;
    }    public void m() {System.out.println("do method");}
}

定义了一个静态内部类,当JVM加载Mgr07这个类时,是不会加载其内部类的,也就是说,只有调用getInstance方法时,才会加载Mgr07Holder,同时初始化INSTANCE对象。

这么一来,既能保证INSTANCE的单例,也可以实现懒加载。代码也很简单清晰。是完美的解决方式之一。

8、枚举单例

先看代码

public enum Mgr08 {
    INSTANCE;    public void m{System.out.println("do method");}
}

超级精简的实现。这是SUN发布的《Effective JAVA》中推荐的一种写法。利用枚举类型的特性。是完美的解决方式之一。

(0)

相关推荐

  • java常见设计模式之---单例模式

    java常见设计模式之---单例模式 1.单例模式简介 应用场景举例 2.单例模式的特点 3.单例模式和静态类 4.单例模式的经典实现 饿汉式单例(典型实现) 饿汉式-静态代码块 懒汉式单例创建,五种 ...

  • 设计模式笔记(一):Singleton 设计模式

    今天开始学习设计模式,借此机会学习并整理学习笔记. 设计模式是一门不区分语言的课程,什么样的编程语言都可以用到设计模式.如果说java语法规则比作武功招式的话,那么设计模式就是心法. 设计模式共有23 ...

  • 设计模式之单例模式

    目录: 什么是单例模式 单例模式的应用场景 单例模式的优缺点 单例模式的实现 总借 一.什么是单例模式 单例模式顾名思义就是只存在一个实例,也就是系统代码中只需要一个对象的实例应用到全局代码中,有点类 ...

  • 23种设计模式入门 -- 单例模式

    单例模式:采用一定的方法,使得软件运行中,对于某个类只能存在一个实例对象,并且该类只能提供一个取得实例的方法. 分类: 饿汉式 静态常量方式 静态代码块方式 懒汉式 普通方式,线程不安全 同步方法方式 ...

  • 设计模式:单例模式 (关于饿汉式和懒汉式)

    定义 单例模式是比较常见的一种设计模式,目的是保证一个类只能有一个实例,而且自行实例化并向整个系统提供这个实例,避免频繁创建对象,节约内存. 单例模式的应用场景很多, 比如我们电脑的操作系统的回收站就 ...

  • 单例模式的八种写法

    单例模式作为日常开发中最常用的设计模式之一,是最基础的设计模式,也是最需要熟练掌握的设计模式.单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点.那么你知道单例模式有多少种实现方式 ...

  • 详解JAVA面向对象的设计模式 (七)、装饰模式

    装饰模式 Decorator 装饰模式比较简单,我就不单独写实现例子了.参考设计图去实现不是什么问题.建议可以写一写找找感觉. 在现实生活中,常常需要对现有产品增加新的功能或美化其外观,如房子装修.相 ...

  • 详解JAVA面向对象的设计模式 (四)、外观模式

    外观模式 Facade 外观模式内容相对简单,就不写新的例子了.本篇文章摘录自 http://c.biancheng.net/view/1369.html 外观模式的定义与特点 外观(Facade)模 ...

  • 详解JAVA面向对象的设计模式 (二)、策略模式

    策略模式 Strategy 介绍 在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改.这种类型的设计模式属于行为型模式. 在策略模式中,我们创建表示各种策略的对象和 ...

  • 3W 字详解 Java 集合

    开源前哨 93篇原创内容 公众号 数据结构作为每一个开发者不可回避的问题,而 Java 对于不同的数据结构提供了非常成熟的实现,这一个又一个实现既是面试中的难点,也是工作中必不可少的工具,在此,笔者经 ...

  • 从零开始学Java(九)详解Java中的方法

    方法 1.什么是方法,有什么用? (可以先看一下一个程序如果没有方法,会出现什么问题?) 方法(英语单词:method)是可以完成某个特定功能的并且可以被重复利用的代码片段. 方法的出现,让代码具有了 ...

  • 从零开始学Java(十四)详解Java中的static关键字(上)

    ✔上一篇Java零基础系列文章我们说到了Java种this关键字的使用,今天继续来说说Java中的static关键字,篇幅较多,分为上下两篇更新,这篇文章主要内容: Java中static关键字 Ja ...

  • 从零开始学Java(十四)详解Java中的static关键字(下)

    接上篇Java-static关键字(上),今儿继续写完,这篇文章主要内容如下: Java static静态代码块 Java static静态方法 Java static静态代码块 静态代码块的语法格式 ...

  • 详解Java的自动装箱与拆箱(Autoboxing and unboxing)

    详解Java的自动装箱与拆箱(Autoboxing and unboxing) 一.什么是自动装箱拆箱  很简单,下面两句代码就可以看到装箱和拆箱过程 1 //自动装箱2 Integer total ...

  • 图文详解Java对象内存布局

    作为一名Java程序员,我们在日常工作中使用这款面向对象的编程语言时,做的最频繁的操作大概就是去创建一个个的对象了.对象的创建方式虽然有很多,可以通过new.反射.clone.反序列化等不同方式来创建 ...