单例模式
介绍
概述:单例(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 }