【摘要】单例模式Singleton就是某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
前言
当在搜索引擎输入”单例模式”这四个字,我们一定可以看到五花八门关于单例模式的分类,最常见的莫过于饿汉式和懒汉式,还有什么5种的,6种,7种的。要是觉得这很重要的话,不妨考虑一下”茴香豆”的”茴”下面的”回”到底有几种写法。再说个题外话,记得高中时期班上有位同学天天吹嘘自己可以用N英文表示中文中”牛”字的表达方式。哈哈哈,有点扯远了。学习设计模式的过程中不断的深入理解面向对象的思想,灵活地运用到解决实际问题的场景下,这才是我们所需要的。
单例模式(Singleton Pattern)
单例模式属于创建型模式,它提供了一种创建对象的最佳方式。
1.单例类只能有一个实例。
2.必须保证自己创建自己的唯一实例。
3.并且给其他对象提供访问这一实例的方式。
应用场景
1.要求生产唯一序列号。
2.WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
3.创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
分类
单例模式常见的有饿汉式、懒汉式、双检锁/双重校验锁、静态内部类、枚举。
饿汉式
这种方式类加载到内存后,就实例化一个单例,JVM会保证线程安全。简单实用,相当推荐使用。但是,他的唯一缺点就是:不管是否用到,类加载时就会完成实例化。
1 2 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一个单例。和上面的本质没有区别,看看就好。
1 2 3 4 5 6 7 8
| public class Singleton02 { private static final Singleton02 INSTANCE;
static { INSTANCE = new Singleton02(); } }
|
懒汉式(线程不安全)
相比饿汉式,懒汉式的单例模式达到了按需初始化的目的,但是带来了线程不安全的问题。
1 2 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就会发现不同线程访问到的实例并不全是同一个对象实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 422057313 503674368 1650594751 1634218452 1160999711 1341620907 1522804050 72786614 1430746659 966186071 1133046463 151286434 2115147268 1326204679 422057313 796465865 796465865 733365800 796465865 796465865 ...
|
懒汉式(线程安全)
为了保证懒汉式单例模式线程安全,考虑到对getInstance()方法的改造。通过synchronized
来实现线程安全。
1 2 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对象大的多,还有就是在每一次使用的时候都需要去获取这把锁才可以获取实例。这样一来,效率就会降低。
所以就有人想通过减少同步代码块的方式来提高效率,所以就产生了下面这种。
1 2 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)这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
1 2 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
,只有一个线程可以获得对象的初始化锁,其他线程无法进行初始化,保证对象的唯一性。
1 2 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(); } } }
|
枚举单例模式
1 2 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.