GOF 创建型模式 系列文章
1. 概述
-
单例模式的核心作用
- 保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
-
优点
- 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动
时直接产生一个单例对象,然后永久驻留内存的方式来解决 - 单例模式可以在系统设置全局的访问点,优化共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理
- 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动
-
常见的5种实现方式
-
主要
- 饿汉式 ( 线程安全,调用效率高。但是,不能延时加载。)
- 懒汉式 ( 线程安全,调用效率不高,资源利用率高。但是,可以延时加载。)
-
其他
- 双重检测锁式 ( 由于JVM底层内部模型原因,偶尔会出问题。不建议使用)
- 静态内部类式 ( 线程安全,调用效率高。但是 ,可以延时加载 )
- 枚举单例 ( 线程安全,调用效率高,不能延时加载)
-
-
如何使用
- 单例对象 占用资源少,不需要延时加载:枚举 优于 饿汉式
-
- 单例对象 占用资源大,需要延时加载:静态内部类 优于 懒汉式
2. 饿汉式
- static变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的问题。
虚拟机保证只会装载一次该类 ,肯定不会发生并发访问的问题。因此,可以省略synchronized关键字。 - 问题:如果只是加载本类,而不是要调用getInstance(),甚至永远没有调用,则会造成资源浪费!
public class singletonDemo1 {
//构造器私有化
private singletonDemo1(){}
// 在类装载时初始化
// 线程安全
private static singletonDemo1 instance = new singletonDemo1();
// 虚拟机保证只会装载一次该类 ,肯定不会发生并发访问的问题
// 调用效率高
private static singletonDemo1 getInstance(){
return instance;
}
}
3. 懒汉式
- 懒加载在用的时候加载,资源利用率高,但每次调用
getInstance
方法都要同步,并发效率低。
public class singletonDemo2 {
//构造器私有化
private singletonDemo2(){}
// 在类装载时初始化
// 线程安全
private static singletonDemo2 instance=null;
// 虚拟机保证只会装载一次该类 ,肯定不会发生并发访问的问题
// 调用效率高
private static synchronized singletonDemo2 getInstance(){
if(instance == null){
instance = new singletonDemo2();
}
return instance;
}
}
4. 双重检测锁式
- 将同步内容下方到
if
内部,提高了执行的效率不必每次获取对象时都进行同步,只有第一次才同步创建了以后就没必要了。 - 问题:
- 由于编译器优化原因和JVM底层内部模型原因,偶尔会出问题。不建议使用。
public class singletonDemo3 {
//构造器私有化
private singletonDemo3(){}
// 在类装载时初始化
// 线程安全
private static singletonDemo3 instance=null;
// 虚拟机保证只会装载一次该类 ,肯定不会发生并发访问的问题
// 调用效率高
private static synchronized singletonDemo3 getInstance(){
if(instance == null){
singletonDemo3 s3;
synchronized (singletonDemo3.class){
s3 = instance;
if(s3 == null){
synchronized (singletonDemo3.class){
s3 = new singletonDemo3();
}
instance = s3;
}
}
}
return instance;
}
}
5. 静态内部类(也是一种懒加载方式)
- 外部类没有
static
属性,则不会像饿汉式那样立即加载对象。 - 只有真正调用getInstance(),才会加载静态内部类。加载类时是线程安全的。
instance
是static final
类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性。 - 兼备了并发高效调用和延迟加载的优势!
public class singletonDemo4 {
//构造器私有化
private singletonDemo4(){}
// 在类装载时初始化
// 线程安全
private static class SingletonClassInstance{
private static final singletonDemo4 instance = new singletonDemo4();
}
// 虚拟机保证只会装载一次该类 ,肯定不会发生并发访问的问题
// 调用效率高
private static singletonDemo4 getInstance(){
return SingletonClassInstance.instance;
}
}
6. 枚举单例
- 线程安全,调用效率高,不能延时加载
public enum singletonDemo5 {
// 枚举元素本身就是单例模式
INSTANCE;
// 添加自己需要的操作
public void singletonOperation(){
}
}
7. 防止反射与反序列化破坏单例模式
-
问题
- 反射可以破解上面几种实现方式( 不包含枚举 )
- 反序列化可以破解上面几种实现方式( 不包含枚举 )
- 可以通过定义readResolve()防止获得不同对象。
- 反序列化时,如果对象所在类定义了
readResolved
(实际是一种 回调 ),定义返回哪个对象。
-
解决
- 防反射:在构造方法中手动抛出异常控制
- 防反序列化: 定义
readResolve()
则直接返回方法制定的对象
以懒汉式单例为例:
import java.io.ObjectStreamException;
import java.io.Serializable;
public class singletonDemo6 implements Serializable {
//构造器私有化
private singletonDemo6(){
// 防止跳出单例
if(instance!=null)
throw new RuntimeException();
}
// 在类装载时初始化
// 线程安全
private static singletonDemo6 instance = null;
// 虚拟机保证只会装载一次该类 ,肯定不会发生并发访问的问题
// 调用效率高
public static synchronized singletonDemo6 getInstance(){
if(instance == null){
instance = new singletonDemo6();
}
return instance;
}
// 反序列化时,如果定义了readResolve()则直接返回方法制定的对象,而不需要单独再创建新对象
private Object readResolve() throws ObjectStreamException{
return instance;
}
}