题引:
总结自己学习Java常用设计模式后的理解,本篇为单例模式。
未知的事物往往令人不知所措,为了透彻理解单例模式,我们必须知道:
什么是Java中的单例模式?
百度百科给出了Java中单例模式概念的内涵,Java中单例模式定义:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。”
由定义我们可以很清晰的抽象出:
实现Java单例模式类有哪些通用设计规则?
(1)私有化类构造器。
(2)定义静态私有的类对象。
(3)提供公共静态的获取该私有类对象的方法。
了解了单例模式的概念,以及单例模式的通用设计规则,对于如何实现一个Java单例,相信我们已不再感觉束手无策。但在了解具体实现之前,我们还需要了解:
Java单例模式解决了什么问题?
Java的单例模式主要解决了多线程并发访问共享资源的线程安全问题。
Java单例模式主要应用场景有哪些?
1.共享资源的访问与操作场景,如Windows系统的资源管理器,Windows系统的回收站,显卡的驱动程序,系统的配置文件,工厂本身(类模板),应用程序的日志对象等。
2.控制资源访问与操作的场景,如数据库的连接池,Java的线程池等。
单例模式的命名
单例的命名通常包含 singleton(以 singleton 开头或结尾) 或能按名称实际意义区分出在应用中唯一。
了解了Java单例模式出现的缘由以及出现的场合,那么,Java的单例究竟是以怎样的方式出现在这些场合中呢?
Java中单例模式的常用实现方式有哪些?
本文主要介绍4种常用的Java单例实现方式:饿汉式、懒汉式、注册登记式、序列化与反序列化式。
饿汉式
饿汉式,顾名思义,就是指在JVM首次访问到该单例类时,就会把该单例类对象创建出来,并保存在内存中,不管后续是否会使用到这个单例。
/**
* @author jiangsj
* 饿汉式
* 优点:
* (1) 没有加任何的锁,执行效率较高
* (2) 线程安全
* 缺点:
* (1) 类加载的时候就进行了初始化,程序后续未必会使用到该实例,导致内存浪费
* (2) 反射可破坏单例
*/
public class HungrySingleton {
/** 私有化类构造器 */
private HungrySingleton() {}
/** 定义静态私有类对象 */
private static HungrySingleton instance = new HungrySingleton();
/** 提供公共静态的获取该私有类对象的方法 */
public static HungrySingleton getInstance() {
return instance;
}
}
饿汉式-测试类
懒汉式
懒汉式,相比于饿汉式,它是指JVM首次访问到该单例类时,并不会实例化该单例类对象,只有等到后续被外部调用时,才会实例化该单例类对象。
饿汉式的写法比较固定,懒汉式由于延时加载的特性,写法上有一些变化,一般来说,懒汉式存在4个变种。
懒汉式1——无锁懒汉式
/**
* @author jiangsj
* 懒汉式——无锁,线程不安全
* 优点:
* 由于懒汉式延时加载特性,使用该实例时才实例化,节省了内存资源
*
* 缺点:
* (1) 该种实现方式存在线程不安全问题
* (2) 反序列化,反射与克隆可破坏单例
*/
public class LazySingletonWithoutSync {
/** 私有化类构造器 */
private LazySingletonWithoutSync() {}
/** 定义静态私有类对象 */
private static LazySingletonWithoutSync instance;
/** 提供公共静态的获取该私有类对象的方法 */
public static LazySingletonWithoutSync getInstance() {
if (instance == null) {
instance = new LazySingletonWithoutSync();
}
return instance;
}
}
无锁懒汉式-测试类
懒汉式2——Sync同步锁懒汉式
/**
* @author jiangsj
* 懒汉式
* 优点:
* (1) 由于懒汉式延时加载特性,使用该实例时才实例化,节省了内存资源
* (2) 线程安全
* 缺点:
* (1) 给获取实例的公共方法加上同步锁synchronized,性能受到影响
* (2) 反序列化,反射与克隆可破坏单例
*/
public class LazySingletonWithSync {
/** 私有化类构造器 */
private LazySingletonWithSync() {}
/** 定义静态私有类对象 */
private static LazySingletonWithSync instance;
/** 提供公共静态的获取该私有类对象的方法 */
public static synchronized LazySingletonWithSync getInstance() {
if (instance == null) {
instance = new LazySingletonWithSync();
}
return instance;
}
}
Sync同步锁懒汉式-测试类
懒汉式3——双重锁检查
/**
* @author jiangsj
* 懒汉式——双重锁检查单例
* 优点:
* (1) 由于懒汉式延时加载特性,使用该实例时才实例化,节省了内存资源
* (2) 线程安全
* 缺点:
* (1) 如果不加volatile关键词防止指令重排,双重锁检查单例可能会出现不完整实例
* 分析:instance = new LazySingletonWithDoubleCheck() 操作并非原子操作,它包含如下三个操作指令:
* 1) 分配对象的内存空间 memory = allocate()
* 2) 初始化对象 ctorInstance(memory)
* 3) 设置instance指向刚分配的内存地址 instance = memory
* 经过指令重排序后,执行顺序可能如下:
* 1) 分配对象的内存空间 memory = allocate()
* 2) 设置instance指向刚分配的内存地址 instance = memory
* 3) 初始化对象 ctorInstance(memory)
* 若有A线程执行完上述重排序后的第二步,尚未初始化对象,此时B线程来获取单例instance,会发现instance不为空,于是返回该值,但实际该instance尚未构建完成,为不完整实例。
* (2) 反序列化,反射与克隆可破坏单例
*/
public class LazySingletonWithDoubleCheck {
/** 私有化类构造器 */
private LazySingletonWithDoubleCheck() {}
/** 定义静态私有类对象 */
private static volatile LazySingletonWithDoubleCheck instance;
/** 提供公共静态的获取该私有类对象的方法 */
public static LazySingletonWithDoubleCheck getInstance() {
if (instance == null) {
synchronized (LazySingletonWithDoubleCheck.class) {
if (instance == null) {
instance = new LazySingletonWithDoubleCheck();
}
}
}
return instance;
}
}
双重锁检查懒汉式-测试类
懒汉式4——静态内部类
/**
* @author jiangsj
* 懒汉式——静态内部类单例
* 优点:
* (1) 由于懒汉式延时加载特性,使用该实例时才实例化,节省了内存资源
* (2) 在外部类被调用的时候内部类才会被加载,内部类要在方法调用之前初始化,巧妙地避免了线程安全问题
* (3) 兼顾了synchronized的性能问题
* 缺点:
* 反序列化,反射与克隆可破坏单例
*/
public class LazySingletonWithInnerClass {
/** 私有化类构造器 */
private LazySingletonWithInnerClass() {}
/** 使用内部类定义静态私有 LazySingletonWithInnerClass 类对象 */
private static class LazyHolder {
private static final LazySingletonWithInnerClass INSTANCE = new LazySingletonWithInnerClass();
}
/** 提供公共静态的获取该私有类对象的方法 */
public static LazySingletonWithInnerClass getInstance() {
return LazyHolder.INSTANCE;
}
}
静态内部类懒汉式-测试类
注册登记式
每使用一次,都往一个固定的容器中去注册并将使用过的对象进行缓存,下次去取对象的时候,就直接从缓存中取值,以保证每次获取的都是同一个对象。Spring IOC中的单例模式,就是典型的注册登记式单例。
/**
* @author jiangsj
* 注册登记式——map容器单例
*/
public class RegisterSingletonFromMap {
/** 私有化类构造器 */
private RegisterSingletonFromMap() {}
/** 使用 ConcurrentHashMap 容器,装载 RegisterSingletonFromMap 类对象 */
private static Map map = new ConcurrentHashMap();
/** 提供公共静态的获取该私有类对象的方法 */
public static RegisterSingletonFromMap getInstance() {
String className = RegisterSingletonFromMap.class.getName();
synchronized (RegisterSingletonFromMap.class) {
if (!map.containsKey(className)) {
map.put(className, new RegisterSingletonFromMap());
}
}
return map.get(className);
}
}
map容器单例-测试类
注册登记式还有一种写法,枚举型单例
/**
* @author jiangsj
* 注册登记式——枚举单例
*/
public class RegisterSingletonFromEnum {
/** 私有化类构造器 */
private RegisterSingletonFromEnum() {}
/** 使用 enum 实例特性创建 RegisterSingletonFromEnum 类对象 */
private enum Singleton {
INSTANCE;
private RegisterSingletonFromEnum instance;
// JVM保证此方法只调用一次
Singleton() {
instance = new RegisterSingletonFromEnum();
}
public RegisterSingletonFromEnum getInstance() {
return instance;
}
}
/** 提供公共静态的获取该私有类对象的方法 */
public static RegisterSingletonFromEnum getInstance() {
return Singleton.INSTANCE.getInstance();
}
}
枚举型单例-测试类
序列化与反序列化式
序列化与反序列化式单例,需要重写readResolve()方法。
/**
* @author jiangsj
* 序列化与反序列化式单例
*/
public class SerializableSingleton implements Serializable {
/** 私有化类构造器 */
private SerializableSingleton() {}
/** 定义静态私有类对象 */
private static final SerializableSingleton INSTANCE = new SerializableSingleton();
/** 提供公共静态的获取该私有类对象的方法 */
public static SerializableSingleton getInstance() {
return INSTANCE;
}
/** 重写readResolve()方法,保证反序列化生成对象时获得的是同一个对象 */
private Object readResolve() throws ObjectStreamException {
return INSTANCE;
}
}
序列化与反序列化式单例-测试类
上述所有单例的测试类(序列化与反序列化式单例测试类除外)都包含如下单元测试方法:
concurrentGetSingletonWithCyclicBarrierTest()
concurrentGetSingletonWithCountDownLatchTest()
breakSingletonByReflectionTest()
序列化与反序列化式单例测试类包含如下测试方法:
serializableSingletonTest()
单例模式要点总结
- Singleton 模式中的实例构造器可以设置为 protected 以允许子类派生。
- Singleton 模式一般不要实现 Clone 接口,因为这有可能导致多个对象实例,与 Singleton 模式的初衷违背。
- 如何实现多线程环境下安全的 Singleton?需注意对双检查锁的正确实现。
结束语
以上便是我对Java单例模式学习的总结,如有疏漏或错误之处,欢迎大家留言指正。