一、简介
1.什么是设计模式
设计模式是一套被 反复使用、多数人知晓、经过分类编目的、代码设计经验的总结。
2.为什么要使用设计模式
为了可重用代码,让代码更容易的被他人理解并保证代码的可靠性。
二、GOF23
创建型模式:
单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式
结构性模式:
适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
行为型模式
模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、责任链模式、访问者模式
三、单例模式概念及其实现
核心作用:
- 保证一个类只有一个实例,并且提供一个访问该实例的全局访问点
常见应用场景
1.Windows的Task Manager(任务管理器)就是很经典的单例模式
2.Windows的Recycle Bin(回收站)也是典型的单例模式应用,在整个系统运行中,回收站一直维护者仅有的一个实例
3.网站的计数器,一般也采用单例模式,否则难以同步
4.数据库的连接池设计一般也采用单例模式,因为数据库连接是一种数据库资源
5.在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理
单例模式的优点
1.由于单例模式只生成一个实例,减少了系统性能开销。
2.单例模式可以在系统设置全局访问点,优化环共享资源访问。
单例模式的五种实现方式
主要
- 饿汉式(线程安全,调用效率高。但是不能延时加载)
- 懒汉式(线程安全,调用效率不高。但是可以延时加载)
其他
双重检测锁式(由于JVM底层内部模型原因,偶尔会出现问题,不建议使用)
静态内部类式(线程安全,调用效率高,可以延时加载)
枚举单例(线程安全,调用效率高,不能延时加载)
-
饿汉式实现
/**
* 饿汉式单例模式
*/
public class SingletonDemo01 {
//1.将实例设置为静态、私有的
//类初始化时立即加载
private static SingletonDemo01 instance = new SingletonDemo01();
//2.将构造器私有
private SingletonDemo01(){
}
//3.提供访问点
public static SingletonDemo01 getInstance(){
return instance;
}
}
在公有方法处不需要添加synchronized
,因为静态对象在类加载时进行初始化,此时天然线程安全,但是没有延时加载,如果最终资源没有被使用,会造成资源的浪费。因为没有加同步,所以执行效率高。
-
懒汉式实现
/**
* 懒汉式单例模式
*/
public class SingletonDemo02 {
private static SingletonDemo02 instance;
private SingletonDemo02(){
}
public static synchronized SingletonDemo02 getInstance(){
if (instance == null){
instance = new SingletonDemo02();
}
return instance;
}
}
懒汉式在公有方法处需要加synchronized
关键字,因为不添加同步的话,在多线程情况下有可能会对单例模式造成破坏。懒汉式加载可以避免资源的浪费,但是因为加了同步,所以调用效率比较低。
-
双重检测锁实现
/**
* 双重检测锁式
*/
public class SingletonDemo03 {
private static SingletonDemo03 instance = null;
private SingletonDemo03(){}
public static SingletonDemo03 getInstance(){
if (instance == null){
SingletonDemo03 sc;
synchronized (SingletonDemo03.class){
sc = instance;
if (sc == null){
synchronized (SingletonDemo03.class){
if (sc == null){
sc = new SingletonDemo03();
}
}
instance = sc;
}
}
}
return instance;
}
}
优点:
- 调用效率高、线程安全、实现了延迟加载
缺点:
- 由于JVM模型的原因,有时候会出现错误,所以不建议使用
-
静态内部类式
public class SingletonDemo04 {
private SingletonDemo04(){}
private static class SingletonClassInstance{
private static final SingletonDemo04 instance = new SingletonDemo04();
}
public static SingletonDemo04 getInstance(){
return SingletonClassInstance.instance;
}
}
要求:
外部没有static属性,则不会像饿汉式那样立即加载对象。
只有真正调用
getInstance()
方法,才会加载静态内部类,加载类时是线程安全的。instance
是static final
类型,保证内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程的安全。兼备了线程安全、调用效率高、延迟加载的优势。
-
枚举方式
public enum SingletonDemo05 {
/**
* 定义一个枚举元素,他就代表了Singleton的一个实例
*/
INSTANCE;
}
优点:
- 实现简单
- 枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞。
缺点:
- 无延迟加载
如何选用
- 单例对象、占用资源少、不需要延迟加载时
使用枚举式好于饿汉式 - 单例对象、占用资源大、需要延迟加载时
静态内部类式好于懒汉式
关于单例模式的破解
注意:枚举方式由于JVM底层的原因,所以无法破解
方式一:通过反射
//通过反射方式直接调用私有构造器
Class<SingletonDemo01> clazz = (Class<SingletonDemo01>) Class.forName("com.hxx.singleton.SingletonDemo01");
//获取构造器
Constructor<SingletonDemo01> constructor = clazz.getDeclaredConstructor(null);
constructor.setAccessible(true);
//通过构造器创建实例
SingletonDemo01 s3 = constructor.newInstance();
SingletonDemo01 s4 = constructor.newInstance();
System.out.println(s3 == s4);
方式二:通过反序列化
//通过反序列化方式调用
FileOutputStream fos = new FileOutputStream("D:/a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s1);
oos.close();
fos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/a.txt"));
SingletonDemo01 s3 = (SingletonDemo01) ois.readObject();
System.out.println(s3);
解决破解
破解反射
//2.将构造器私有
private SingletonDemo01(){
if (instance != null){
throw new RuntimeException();
}
}
在构造器中对instance
进行判断
破解反序列化
` private Object readResolve(){
return instance;
}
在单例模式的方法中添加readResolve
方法。