1.概述
设计模式在粒度和抽象层次上各不相同,因此从不同的角度,分类形式也不同,目前存在两种较为经典的划分方式,即根据模式作用的范围、模式的目的来划分。根据模式主要是用于类还是用于对象,可将其划分为类模式和对象模式。类模式用于处理类与子类之间的关系,这些关系通过继承建立,体现了静态性。对象模式用于处理对象之间的关系,在运行时刻可以发生变化,体现了动态性。在某种程度上来说,继承机制几乎被所有模式使用,大多数模式都属于对象模式,小部分属于类模式。根据模式目的可将模式分为创建型模式、结构型模式和行为型模式。本文主要讲解创建型模式的单例模式。
2.单例模式
2.1 设计模式分类
1.创建型模式
创建型模式又分为创建型类模式和创建型对象模式,创建型类模式将对象的部分创建工作延迟到子类,创建型对象模式则将它延迟到另一个对象中。
2.结构型模式
结构型模式主要用于处理类和对象的组合,结构型类模式使用继承机制来组合类,结构型对象描述则描述了对象的组装方式。
3.行为型模式
行为型模式描述了类或对象之间的交互或职责分配,行为型类模式使用继承描述算法或控制流,行为型对象模式描述了一组对象怎样协作完成单个对象无法完成的任务。
2.2 单例模式
定义:采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个方法来保证其取得对象实例。单例模式共有8种,如下:
(1)饿汉式(静态常量)
(2)饿汉式(静态代码块)
(3)懒汉式(线程不安全)
(4)懒汉式(线程安全,同步方法)
(5)懒汉式(线程安全,同步代码块)
(6)双重检查
(7)静态内部类
(8)枚举
2.2.1 饿汉式(静态常量)
public class SigletonStatic {
private SigletonStatic() {
}
private static final SigletonStatic INSTANCE = new SigletonStatic();
public static SigletonStatic getInstance() {
return INSTANCE;
}
}
优势: 写法比较简单,在类装载的时候就完成了对象的实例化,避免了线程同步问题;
缺点:在类装载的时候就完成了对象的实例化,没有达到懒加载的效果,如果一直没有使用这个对象实例,则会造成内存浪费。
结论:这种单例模式可用,但是可能会造成内存浪费。
2.2.2 饿汉式(静态代码块)
public class SigletonStatic {
private SigletonStatic() {
}
private static final SigletonStatic INSTANCE;
static {
INSTANCE = new SigletonStatic();
}
public static SigletonStatic getInstance() {
return INSTANCE;
}
}
优势: 写法比较简单,在类装载的时候就完成了对象的实例化,避免了线程同步问题;
缺点:在类装载的时候就完成了对象的实例化,没有达到懒加载的效果,如果一直没有使用这个对象实例,则会造成内存浪费。
结论:这种单例模式可用,但是可能会造成内存浪费。
这种写法和2.2.1节方式基本一致,唯一区别在于将对象的实例化放在了静态代码块中,所有基本优缺点是一致的。
2.2.3 懒汉式(线程不安全)
public class SigletonStatic {
private SigletonStatic() {
}
private static SigletonStatic INSTANCE;
public static SigletonStatic getInstance() {
//判断INSTANCE是否为空,为空再创建
if (INSTANCE == null) {
INSTANCE = new SigletonStatic();
}
return INSTANCE;
}
}
优点:实现了懒加载的效果,但是只能在单线程情况下使用;
缺点:在多线程情况下,可能存在线程安全问题,当前一个线程进入if (INSTANCE == null)语句得出结果为true时,还未来得及执行创建对象的代码,另一个线程也通过了这段代码,导致生成了多个对象实例。
结论:多线程情况下,不建议使用。
2.2.4 懒汉式(线程安全,同步方法)
public class SigletonStatic {
private SigletonStatic() {
}
private static SigletonStatic INSTANCE;
//加入同步处理代码块,解决线程安全问题
public static synchronized SigletonStatic getInstance() {
if (INSTANCE == null) {
INSTANCE = new SigletonStatic();
}
return INSTANCE;
}
}
优势: 解决了懒加载和线程安全问题;
缺点:效率较低,每次来获取INSTANCE对象时,都需要加锁,但其实只是第一次创建对象时需要加锁。
结论:效率较低,不建议使用。
2.2.5 懒汉式(线程安全,同步代码块)
public class SigletonStatic {
private SigletonStatic() {
}
private static SigletonStatic INSTANCE;
public static SigletonStatic getInstance() {
if (INSTANCE == null) {
synchronized (SigletonStatic.class) {
INSTANCE = new SigletonStatic();
}
}
return INSTANCE;
}
}
优势:这种方式本意是为了改良2.2.4种的方法,有一定的效率提示;
缺点: 并不能完全解决线程安全问题,与2.2.3节类似,可能存在创建多个对象实例;
结论:不够安全,不建议使用。
2.2.6 双重检查
public class SigletonStatic {
private SigletonStatic() {
}
//volatile修饰,保证线程之间可见性
private static volatile SigletonStatic INSTANCE;
public static SigletonStatic getInstance() {
if (INSTANCE == null) {
synchronized (SigletonStatic.class) {
//二次校验,保证对象只创建一次
if (INSTANCE == null) {
INSTANCE = new SigletonStatic();
}
}
}
return INSTANCE;
}
}
优势:保证了INSTANCE只被实例化了一次,同时保证了线程安全,保证了延时加载,效率较高;
缺点:相对完美的方案;
结论:建议开发中使用。
2.2.7 静态内部类
public class SigletonStatic {
private SigletonStatic() {
}
//写一个静态内部类,类中包含属性SigletonStatic
private static class SigletonInstance {
private static final SigletonStatic INSTANCE = new SigletonStatic();
}
//提供一个静态的公有方法
public static SigletonStatic getInstance() {
return SigletonInstance.INSTANCE;
}
}
优势:这种方式采用了类加载的机制来保证初始化时只有一个线程,静态内部类SigletonInstance在SigletonStatic 被加载时不会立即被实例化,而是在调用getInstance()方法时才会被装载,从而创建INSTANCE 对象;类的静态属性INSTANCE 只会在第一次被装载时实例化,因此JVM帮我们保证了线程安全,在类SigletonInstance初始化时,别的线程无法进入;避免了线程不安全,利用静态内部类实现了延时加载;
结论:建议开发中使用。
2.2.8 枚举
enum SigletonStatic {
INSTANCE;
private void method() {
System.out.println("method done!");
}
}
优势:借助于枚举来实现单例模式,不仅避免了多线程的同步问题,还能防止反序列化重新创建新的对象。
结论:建议开发中使用。
3.小结
1.单例模式的创建主要包括懒汉式和饿汉式,饿汉式是类加载时就进行对象实例化,懒汉式是使用时才创建对象;
2.饿汉式可能出现对象提前创建而未使用,导致资源浪费的情况;
3.懒汉式可能存在线程安全问题,因此建议使用双重检查、静态内部类、枚举等方式来创建单例对象。
4.参考文献
1.《设计模式-可复用面向对象软件的基础》-Erich Gamma、Richard Helm、Ralph Johnson、John Vlissides
2.《可复用物联网Web3D框架的设计与实现》-程亮(知网)
3.https://www.bilibili.com/video/BV1G4411c7N4-尚硅谷设计模式