Java设计模式 - 单例模式(创建型模式)

单例模式我在上学期看一些资料时候学习过,没想到这学期的软件体系结构就有设计模式学习,不过看似篇幅不大,介绍得比较简单,在这里我总结下单例模式,一来整理之前的笔记,二来也算是预习复习课程了。

概述

单例模式 (Singleton Pattern) 是 Java 中最简单的设计模式之一,属于一种创建型模式。

单例模式保证对于每一个类加载器,一个类仅有一个唯一的实例对象,并提供一个全局的唯一访问点。

优缺点

优点:

减少内存开销。

避免对资源的多重占用。

严格控制客户程序访问其唯一的实例。

单例类的子类都是单例类。

比较容易改写为允许一定数目对象的类。

缺点:

不适用于变化的对象。

由于没有抽象层,难以扩展。

职责过重,一定程度上违背了“单一职责原则”。

核心思路

构造器私有化。

私有的静态常量成员。

公开的静态方法getInstance(),返回静态实例对象。

确保每次访问的实例对象都是同一个对象。

多线程中要保证线程安全。

实现方式

实现单例方式有五种:饿汉式、懒汉式、双重校验锁(DCL)、静态内部类、枚举。

饿汉式

优点:在类加载的时候就完成实例化,避免了线程同步问题。

缺点:在类装载的时候就完成实例化,没有达到 Lazy Loading 的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

public class Singleton1 { private static final Singleton1 INSTANCE = new Singleton1(); private Singleton1() { System.out.println("饿汉式,可用"); } public static Singleton1 getInstance() { return INSTANCE; } } 懒汉式

优点:达到了懒加载的效果。

缺点:线程不安全。

对于普通懒汉式,线程不安全。

原因:在进入if (instance == null) 时,可能一个线程进入后就切换到了另一个线程,而此时并未创建实例对象,这个线程又再次进入了if代码块。

public class Singleton2 { private static Singleton2 instance; private Singleton2() { System.out.println("懒汉式,线程不安全,多线程不可用"); } public static Singleton2 getInstance() { if (instance == null) { instance = new Singleton2(); } return instance; } }

对方法添加synchronized保证线程安全。

synchronized保证了getInstance()线程安全,但对方法进行同步效率不高。

public class Singleton3 { private static Singleton3 instance; private Singleton3() { System.out.println("懒汉式(synchronized方法),效率太低"); } public static synchronized Singleton3 getInstance() { if (instance == null) { instance = new Singleton3(); } return instance; } }

改进方案2,使用synchronized代码块。

使用了synchronized代码块改进,但又出现了和方案1一样的线程安全问题。

public class Singleton4 { private static Singleton4 instance; private Singleton4() { System.out.println("懒汉式(synchronized代码块),线程不安全,多线程不可用"); } public static Singleton4 getInstance() { if (instance == null) { synchronized (Singleton4.class) { // 此处若有线程阻塞,其它线程就仍可以进入到了前面if instance = new Singleton4(); } } return instance; } }

双重校验锁(DCL)

针对上一个改进,使用两个if (instance == null) 。

这里先别管实现的Serializable接口和readResolve()方法,这两个用于后面的序列化测试。

另外,此处的volatile声明是很重要的,volatile变量有两种特性。

一是保证了此变量对所有线程的可见性。“可见性”指的是当一条线程修改了这个变量的值,新值对于其它线程来说是可以立即知道的。

二是禁止指令重排序优化。但volatile变量的运行在并发编程下并非是安全的,因为不能保证原子性。详细请自己去看相关资料。

import java.io.Serializable; public class Singleton5 implements Serializable { private static final long serialVersionUID = 1L; private static volatile Singleton5 instance; private Singleton5() { System.out.println("懒汉式优化(Double Check Lock)双重校验锁,推荐用"); } public static Singleton5 getInstance() { if (instance == null) { // 未被初始化,但是无法确定这时其他线程是否已经对其初始化,因此添加对象锁进行互斥 synchronized (Singleton5.class) { // 再一次进行检查,因为有可能在当前线程阻塞的时候,其他线程对instance进行初始化 if (instance == null) { // 此时还未被初始化的话,在这里初始化可以保证线程安全 instance = new Singleton5(); } } } return instance; } // 如果该对象被用于序列化,该方法可以保证对象在序列化前后保持一致 private Object readResolve() { return getInstance(); } } 静态内部类

静态内部类方式在类被加载时并不会立即实例化。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwfgsx.html