【摘要】单例模式Singleton就是某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。

前言
当在搜索引擎输入”单例模式”这四个字,我们一定可以看到五花八门关于单例模式的分类,最常见的莫过于饿汉式和懒汉式,还有什么5种的,6种,7种的。要是觉得这很重要的话,不妨考虑一下”茴香豆”的”茴”下面的”回”到底有几种写法。再说个题外话,记得高中时期班上有位同学天天吹嘘自己可以用N英文表示中文中”牛”字的表达方式。哈哈哈,有点扯远了。学习设计模式的过程中不断的深入理解面向对象的思想,灵活地运用到解决实际问题的场景下,这才是我们所需要的。
单例模式(Singleton Pattern)
单例模式属于创建型模式,它提供了一种创建对象的最佳方式。
1.单例类只能有一个实例。
2.必须保证自己创建自己的唯一实例。
3.并且给其他对象提供访问这一实例的方式。
应用场景
1.要求生产唯一序列号。
2.WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
3.创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
分类
单例模式常见的有饿汉式、懒汉式、双检锁/双重校验锁、静态内部类、枚举。
饿汉式
这种方式类加载到内存后,就实例化一个单例,JVM会保证线程安全。简单实用,相当推荐使用。但是,他的唯一缺点就是:不管是否用到,类加载时就会完成实例化。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 
 | 
 
 public class Singleton01 {
 private static final Singleton01 INSTANCE = new Singleton01();
 
 private Singleton01() {
 }
 
 public static Singleton01 getInstance() {
 return INSTANCE;
 }
 
 public void m() {
 System.out.println("m");
 }
 
 public static void main(String[] args) {
 Singleton01 s1 = Singleton01.getInstance();
 Singleton01 s2 = Singleton01.getInstance();
 System.out.println(s1 == s2);
 }
 }
 
 | 
对于饿汉式的单例模式,还有一种是在静态代码块中进行new一个单例。和上面的本质没有区别,看看就好。
| 12
 3
 4
 5
 6
 7
 8
 
 | public class Singleton02 {private static final Singleton02 INSTANCE;
 
 static {
 INSTANCE = new Singleton02();
 }
 
 }
 
 | 
懒汉式(线程不安全)
相比饿汉式,懒汉式的单例模式达到了按需初始化的目的,但是带来了线程不安全的问题。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 
 | 
 
 
 public class Singleton03 {
 private static Singleton03 INSTANCE;
 
 private Singleton03() {
 }
 
 public static Singleton03 getInstance() {
 if (INSTANCE == null) {
 
 
 
 
 
 INSTANCE = new Singleton03();
 }
 return INSTANCE;
 }
 
 public void m() {
 System.out.println("m");
 }
 
 public static void main(String[] args) {
 for (int i = 0; i < 100; i++) {
 new Thread(() -> {
 System.out.println(Singleton03.getInstance().hashCode());
 }).start();
 }
 }
 }
 
 | 
上面这种方式线程不安全,在判断INSTANCE == null的后面我们把线程阻塞1ms,运行后通过比较hashCode就会发现不同线程访问到的实例并不全是同一个对象实例。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | 422057313503674368
 1650594751
 1634218452
 1160999711
 1341620907
 1522804050
 72786614
 1430746659
 966186071
 1133046463
 151286434
 2115147268
 1326204679
 422057313
 796465865
 796465865
 733365800
 796465865
 796465865
 ...
 
 | 
懒汉式(线程安全)
为了保证懒汉式单例模式线程安全,考虑到对getInstance()方法的改造。通过synchronized来实现线程安全。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 
 | 
 
 public class Singleton04 {
 private static Singleton04 INSTANCE;
 
 private Singleton04() {
 
 }
 
 public static synchronized Singleton04 getInstance() {
 if (INSTANCE == null) {
 
 
 
 
 
 INSTANCE = new Singleton04();
 }
 return INSTANCE;
 }
 
 public void m() {
 System.out.println("m");
 }
 
 public static void main(String[] args) {
 for (int i = 0; i < 100; i++) {
 new Thread(() -> {
 System.out.println(Singleton04.getInstance().hashCode());
 }).start();
 }
 }
 }
 
 | 
运行测试之后,发现这种方式可以保证线程安全。但是在内存当中的对象一定比我们上面这个简单的Singleton04对象大的多,还有就是在每一次使用的时候都需要去获取这把锁才可以获取实例。这样一来,效率就会降低。
所以就有人想通过减少同步代码块的方式来提高效率,所以就产生了下面这种。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 
 | 
 
 public class Singleton05 {
 private static Singleton05 INSTANCE;
 
 private Singleton05() {
 
 }
 
 public static Singleton05 getInstance() {
 if (INSTANCE == null) {
 
 synchronized (Singleton05.class) {
 
 
 
 
 
 INSTANCE = new Singleton05();
 }
 }
 return INSTANCE;
 }
 
 public void m() {
 System.out.println("m");
 }
 
 public static void main(String[] args) {
 for (int i = 0; i < 100; i++) {
 new Thread(() -> {
 System.out.println(Singleton05.getInstance().hashCode());
 }).start();
 }
 }
 }
 
 | 
上面这种并不能保证线程安全。所以就出现了双检锁。
双检锁/双重校验锁
双检锁/双重校验锁(DCL,即 double-checked locking)这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 
 | 
 
 public class Singleton06 {
 private static volatile Singleton06 INSTANCE;
 
 private Singleton06() {
 
 }
 
 public static Singleton06 getInstance() {
 if (INSTANCE == null) {
 
 synchronized (Singleton06.class) {
 if (INSTANCE == null) {
 
 
 
 
 
 INSTANCE = new Singleton06();
 }
 }
 }
 return INSTANCE;
 }
 
 public void m() {
 System.out.println("m");
 }
 
 public static void main(String[] args) {
 for (int i = 0; i < 100; i++) {
 new Thread(() -> {
 System.out.println(Singleton06.getInstance().hashCode());
 }).start();
 }
 }
 }
 
 | 
静态内部类方式
只有第一次调用getInstance()方法时,虚拟机才加载Singleton07Holder内部类 并初始化INSTANCE,只有一个线程可以获得对象的初始化锁,其他线程无法进行初始化,保证对象的唯一性。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 
 | 
 
 
 public class Singleton07 {
 private Singleton07() {
 
 }
 
 private static class Singleton07Holder {
 private final static Singleton07 INSTANCE = new Singleton07();
 }
 
 public static Singleton07 getInstance() {
 return Singleton07Holder.INSTANCE;
 }
 
 public void m() {
 System.out.println("m");
 }
 
 public static void main(String[] args) {
 for (int i = 0; i < 100; i++) {
 new Thread(() -> {
 System.out.println(Singleton07.getInstance().hashCode());
 }).start();
 }
 }
 }
 
 | 
枚举单例模式
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | 
 
 public enum Singleton08 {
 INSTANCE;
 
 public void m() {
 }
 
 public static void main(String[] args) {
 for (int i = 0; i < 100; i++) {
 new Thread(() -> {
 System.out.println(Singleton08.INSTANCE.hashCode());
 }).start();
 }
 }
 }
 
 | 
结束语
文末就不聊单例了。
“茴香豆”的”茴”下面的”回”的几种写法:回、囘、囬、廻;
“牛”字的英文表达方式:ox;(菜牛)beef cattle;(纯种公牛)pedigree bull;(公牛)bull;(母牛)cow;(奶牛)milk cow,dairy cattle;(水牛)water buffalo;(小牛)calf.