Java设计模式之单例模式
前言
单例模式在实际项目中起着非常重要的作用,笔者将从以下几点来讲解。
1.单例模式的概念
2.单例模式的类图
3.单例模式的几种表达形式
单例模式的概念
单例模式保证某一个具体的类只有一个实例,并且只有自己才能实例化,同时向整个系统开放这个实例。
单例模式的类图
1 public class Singleton { 2 3 private static final Singleton instance = new Singleton(); 4 5 private Singleton () { 6 7 } 8 9 public static Singleton getInstance() { 10 return instance; 11 } 12 }
Singleton类称为单例类,同时构造函数是以private修饰,防止外部通过new 关键字自行获得实例,同时提供一个获取实例的共有成员函数。因此可以确保在整个系统中,Singleton有且仅有一个实例。
单例模式的几种表达形式
单例模式在表达形式上可以分为这么几类
1.饿汉式
2.懒汉式
3.双检锁
4.静态内部类的方式
5.静态代码块的方式
6.枚举的方式
下面重点来介绍一下前三种方式。
饿汉式
饿汉式,既然叫饿汉式,指的就是无论在系统中是否用到该实例,这个实例都会在当类被加载器加载时产生。上述的例子就是饿汉式的通用写法。这种写法不会引起资源的竞争,同时会不会存在线程安全的问题呢?
1 public class Singleton { 2 3 private static final Singleton instance = new Singleton(); 4 5 private Singleton () { 6 7 } 8 9 public static Singleton getInstance() { 10 return instance; 11 } 12 }
第3行,当某线程A执行到new Singleton()时,没有实例化完成,线程A切换至线程B,因此内存中会出现两份实例?这两份实例指向不同的内存地址?这不就出现了线程的不安全的问题吗?
懒汉式
1 public class Singleton { 2 3 private static Singleton instance = null; 4 5 private Singleton () { 6 7 } 8 9 public static Singleton getInstance() { 10 11 if(instance == null) { 12 instance = new Singleton(); 13 } 14 return instance; 15 } 16 }
懒汉式和饿汉式刚好相反,懒汉式只有在应用中开始调用该实例时,才回去实例化,同时该实例保存在内存中。不过这种写法基本上在实际项目中不会去采用,因为存在线程安全的问题,当线程A执行到第11行时,线程切换至线程B,线程A和B同时执行new Singleton,因此内存中会出现多份实例的引用,这已经和单例模式的初衷相违背。
双检锁
双检锁的写法是为了解决懒汉式写法的线程不安全。具体代码如下所示:
1 public class Singleton { 2 3 private static Singleton instance = null; 4 5 private static Object monitor = new Object(); 6 7 private Singleton () { 8 9 } 10 11 public static Singleton getInstance() { 12 13 if(instance == null) { 14 15 synchronized (Singleton.class) { 16 17 if(instance == null) { 18 instance = new Singleton(); 19 } 20 } 21 22 } 23 return instance; 24 } 25 }
不过也并不是线程绝对的安全。为了保证在高并发下线程安全,需要在instance之前加上关键字volatile,从而保证在多线程之间切换时的可见性,同时防止指令重排序。
单例模式的优点
1. 因为在整个应用中,有且只有一个实例,减少了内存的开支,避免了同一对象频繁的创建和销毁,所以大大节省了资源的消耗。
2.唯一的实例一旦创建,便会保存在内存中,减少系统的开销,尤其是给对象读取较多的资源,比如读取配置文件,一次读取就可以永久的保存在内存中
3.单例模式保证了对资源的多重占用,比如对一个文件的操作,我们采取线程安全的单例模式进行操作。
单例模式的缺点
1.单例模式是一个单例类,因此没有抽象接口,因此会存在扩展性差的缺点,业务逻辑发生了变化,除了修改类之外没有别的办法,这显然和设计原则中的对修改关闭对扩展开放的原则。