设计模式01——单例模式
- 一、分类(Group Of Four/GOF 23)
- 二、单例模式
一、分类(Group Of Four/GOF 23)
设计模式一定要运用到具体应用中。 创建型模式:单例模式、工厂模式、抽象工厂模式、创建者模式、原型模式。 结构性模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。 行为型模式:模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、责任链模式、访问者模式。
设计模式的要点: 面向接口设计; 复合 has a,继承 is a; 隔离变化:发现变化部分,把它封装起来。
二、单例模式
- 作用 保证一个类只有一个对象,并且提供一个访问该实例的全局访问点。
- 应用场景 任务管理器、配置文件、日志管理、数据库连接池、OS文件系统、Servlet中的Application对象、Spring中的Bean。
- 优点 内存占用和系统开销小。
- 实现 主要: (1)饿汉式 构造器私有,类初始化时(天然线程安全过程)就加载这个对象(static)。 线程安全、调用效率高,不能延时加载。
/**
* 饿汉式单例模式
*/
class SingletonPattern01 {
// 类加载时对象就创建了
private static /*final*/ SingletonPattern01 instance = new SingletonPattern01();
private SingletonPattern01() {
}
public static SingletonPattern01 getInstance() {
return instance;
}
}
(2)懒汉式 懒加载,需要时才加载,但是需要考虑同步synchronized。 线程安全、资源利用率高,延时加载、并发效率低。
代码语言:javascript复制/**
* 懒汉式单例模式
*/
class SingletonPattern02 {
private static SingletonPattern02 instance;
private SingletonPattern02() {
}
// 延时加载
public static synchronized SingletonPattern02 getInstance() {
if (instance == null) {
instance = new SingletonPattern02();
}
return instance;
}
}
其他: (3)双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题,不推荐) 同步方法优化为两个同步块,放在if内部。 隐患:实力对象过程可分为1.分配内存空间;2.初始化对象;3.将对象指向刚分配的内存空间。有些编译器为了性能问题可能将2、3步重排序,导致一个线程访问已分配内存空间的对象,还对象没有完成初始化的情况发生。 解决:给对象添加volatile关键词,将对象存放在内存中而非缓存。
代码语言:javascript复制/**
* 双重检测锁式单例模式
*/
class SingletonPattern03 {
// 添加volatile关键字
private volatile static SingletonPattern03 instance;
private SingletonPattern03() {
}
public static SingletonPattern03 getInstance(){
// 提高检测效率
if (instance == null) {
synchronized (SingletonPattern03.class) {
// 可能有多线程同时进入同步块内
if (instance == null) {
instance = new SingletonPattern03();
}
}
}
return instance;
}
}
(4)静态内部类式(线程安全、调用效率高,延时加载) 优化注意:线程安全、调用效率高、懒加载 静态内部类的加载不需要依附外部类,在使用时才加载,不过在加载静态内部类的过程中也会加载外部类。
代码语言:javascript复制/**
* 静态内部类式单例模式
*/
class SingletonPattern04 {
// 静态内部类在使用时加载,天然线程安全
private static class SingletonPatternClassInstance {
// 保证只有一个,且只能赋值一次
private static final SingletonPattern04 instance = new SingletonPattern04();
}
public static SingletonPattern04 getInstance() {
return SingletonPatternClassInstance.instance;
}
private SingletonPattern04() {
}
}
问题:即使构造器私有了,也可以通过反射来调用。
(5)枚举单例(线程安全、调用效率高,不能延时加载) 可以天然防止反射和反序列化,基于JVM底层实现的。4、5较优。
代码语言:javascript复制/**
* 枚举式单例模式
*/
enum SingletonPattern05 {
// 枚举元素本身就是单例的,INSTANCE就是SingletonPattern05的一个单例
INSTANCE;
// 添加需要的操作
public void singletonPatternOperation() {
}
}
- 反射破解
通过
c.setAccessible(true);
跳过私有构造器保护。
public static void main(String[] args) throw Exception {
Class<SingletonPattern01> clazz = (Class<SingletonPattern01>) Class.forName("com.ustc.designpattern.SingletonPattern01");
Constructor<SingletonPattern01> c = clazz.getDeclaredConstructor(null);
c.setAccessible(true);
SingletonPattern01 s3 = c.newInstance();
SingletonPattern01 s4 = c.newInstance();
}
解决:在私有构造器时添加判断,反射操作时抛出异常。
代码语言:javascript复制private SingletonPattern01() {
if (instance != null) {
throw new RuntimeException();
}
}
- 反序列化破解
public static void main(String[] args) throw Exception {
FileOutputStream fos = new FileOutputStream("${url}");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s1);
oos.close();
fos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("${url}"));
SingletonPattern01 s5 = (SingletonPattern01) ois.readObject();
}
解决:定义readResolve()方法,直接返回此方法中的反序列化对象,而不需要新创建
代码语言:javascript复制private Object readResolve() throw ObjectStreamException {
return instance;
}
- 性能比较
CountDownLatch: 同步辅助类,在完成一组正在其他线程执行的操作之前,它允许一个或多个线程一直等待。 coutDown()当前线程调用此方法,则计数减一,放在finally里执行; await(),调用此方法会一直阻塞当前进程,知道计时器的值为0。