单例模式

Posted lhy-549

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单例模式相关的知识,希望对你有一定的参考价值。

  介绍

  概述:单例(Singleton)模式要求一个类有且仅有一个实例,并且提供了一个全局的访问点。那么问题来了:如何绕过常规的构造器,

     提供一种机制来保证一个类只有一个实例?客户程序在调用某一个类时,它是不会考虑这个类是否只能有一个实例等问题的,

     Singleton模式其实相当于一种职责型模式。因为我们创建了一个对象,这个对象扮演了独一无二的角色。

  • 什么是单例模式?

    保证一个类只有一个实例,并且提供一个访问该实例的全局访问点

  • 有什么作用?

    单例有其独有的使用场景,一般是对于那些业务逻辑上限定不能多例只能单例的情况,例如:类似于计数器之类的存在,

    一般都需要使用一个实例来进行记录,若多例计数则会不准确。

  • 应用场景

    举个例子:

    在我们的windows桌面上,我们打开了一个回收站,当我们试图再次打开一个新的回收站时,Windows系统并不会

    为你弹出一个新的回收站窗口。也就是说在整个系统运行的过程中,系统只维护一个回收站的实例。这就是个单例模式运用。

    就像企业只能有一个老板,有且只有一个。   

  实现方式

  • 饿汉模式

      最基本的思路,就是将类的构造器私有化,那么就不能在外部调用 new 创建实例了。

     其次,通过调用静态方法获取实例。

  

 1 // 一般情况来说,这种方式就够用了!
 2 
 3 public class Boss {
 4     private Boss() {}
 5 
 6     private static Boss instance = new Boss();
 7 
 8     public static Boss getInstance() {
 9         return instance;
10     }
11 }

 

  • 懒汉模式以及演进

 

    饿汉模式的问题在于,即使没有用到 boss,它也会被实例化,有些浪费空间…

    而懒汉模式就是让 boss 只在用到的时候才去加载。

    其设计的思路及代码如下:

    

 1 public class Boss {
 2     // 1. 私有化构造器
 3     private Boss {}
 4 
 5     // 2. 定义实例的变量
 6     private static Boss instance;
 7 
 8     // 3. 通过静态方法创建或返回实例
 9     public static Boss getInstance () {
10         if (instance == null) {
11             instance = new Boss();  // 虽然构造器是私有的,但是可以在内部调用
12         }
13         return instance;
14     }
15 }

  这种方法在单线程下没有任何问题,但是在多线程环境中,却可能会实例化出多个对象。也就是说,它并不是线程安全的。为了解决这个问题,需要对 getInstance 加锁:

  

 1 public class Boss {
 2     // 1. 私有化构造器
 3     private Boss {}
 4 
 5     // 2. 定义实例的变量
 6     private static Boss instance;
 7 
 8     // 3. 通过静态方法创建或返回实例
 9     public synchronized static Boss getInstance () { // 通过锁,将对此方法的调用变成串行的。这就防止了错误
10         if (instance == null) {
11             instance = new Boss();  // 虽然构造器是私有的,但是可以在内部调用
12         }
13         return instance;
14     }
15 }

上述加锁的方式,可以保证正确实例化对象。但是,因为在方法上加了锁,使得获取单例对象的效率过低。这时候,需要兼顾线程安全和效率,就出现了双重检查锁的概念:

 1 // 1. 将构造器私有化
 2 private Boss() {}
 3 
 4 // 2. 初始化一个静态变量
 5 private static volatile Boss instance = null;
 6 
 7 // 3. 构造一个静态方法,通过它初始化或返还对象
 8 public static Boss getInstance() {
 9     // 双重检查锁机制
10     if (instance == null) {
11         synchronized (Boss.class) {
12             if (instance == null) {
13                 instance = new Boss();
14             }
15         }
16     }
17     return instance;
18 }

  其中:

  •  synchronized :块尽量缩小了锁定的范围,提高效率
  •  volatile :是为防止编译器指令重排而导致双重检查锁失效

  另外:

  • 指令重排本是为了优化代码执行效率而存在的,虽然在单线程中效果拔群,但是在多线程中却能带来麻烦。 volatile  可以要求编译器不要做指令重排。
  • 静态内部类(实现)

    这是相对来说,非常优秀的一种实现。在很多地方,推荐使用这种方式。

 1 public class Boss {
 2     // 1. 将构造器私有化
 3     private Boss() { }
 4 
 5     // 2. 充分利用了静态内部类的特性,在里面初始化 Boss 实例
 6     //    - 只会被初始化一次
 7     //    - 只有当静态内部类内部的属性、方法等被调用的时候,静态内部类才会被加载
 8     static class Singleton {
 9         private final static Boss INSTANCE = new Boss();
10     }
11 
12     // 3. 提供一个公共方法,获取实例化好之后的对象
13     public static Boss getInstance() {
14         return Singleton.INSTANCE;
15     }
16 }

 

 

 

  • 枚举类

  ENUM 应该是最简单,也是最好的一种实现单例模式的方式。

  它充分利用了 JVM 的特性,既保证了线程安全,又保证了延迟加载。

 

 1 enum Boss {
 2     INSTANCE;
 3 
 4     public void sayHello () {
 5         System.out.println("hello");
 6     }
 7 }
 8 
 9 public class Main {
10     public static void main (String... args) {
11         Boss theBoss = Boss.INSTANCE;  // 获取实例
12         theBoss.sayHello();            // 调用方法
13     }
14 }

 

 

 

 

 

以上是关于单例模式的主要内容,如果未能解决你的问题,请参考以下文章

常用代码片段

性能比较好的单例写法

片段作为 Android 中的单例

单例片段或保存网页视图状态

你熟悉的设计模式都有哪些?写出单例模式的实现代码

单例模式以及静态代码块