概述
学习设计模式,死记硬背是没用的,还要从实践中理解
日常应用中,设计模式从来都不是单个设计模式独立使用的。在实际应用中,通常多个设计模式混合使用,你中有我,我中有你。下图完整地描述了设计模式之间的混用关系,希望对大家有所帮助。
单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
单例模式的应用场景
对于 Java 来说,单例模式可以保证在一个 JVM 中只存在单一实例。单例模式的应用场景主要有以下几个方面。
- 需要频繁创建的一些类,使用单例可以降低系统的内存压力,减少 GC。
- 某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
- 某些类创建实例时占用资源较多,或实例化耗时较长,且经常使用。
- 某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
- 频繁访问数据库或文件的对象。
- 对于一些控制硬件级别的操作,或者从系统上来讲应当是单一控制逻辑的操作,如果有多个实例,则系统会完全乱套。
- 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。
懒汉模式
延迟加载, 只有在真正使用的时候,才开始实例化。
1)线程安全问题
2)double check 加锁优化
3)编译器(JIT),CPU 有可能对指令进行重排序,导致使用到尚未初始化 的实例,可以通过添加volatile 关键字进行修饰, 对于volatile 修饰的字段,可以防止指令重排。
class LazySingleton{
// java5版本之前 关键字volatile 不能解决指令重排的问题
private volatile static LazySingleton instance;
// 让构造函数为 private,这样该类就不会被实例化
private LazySingleton(){
}
// 这种方式的懒加载只适用于单线程情况下
public static LazySingleton getInstance() {
if (instance==null){
instance=new LazySingleton();
}
return instance;
}
// 同步,线程安全,但是效率太低
// 但是单例模式只有在第一次使用的时候才会创建
// 下面这种写法就不适合了
public static synchronized LazySingleton getInstance() {
if (instance==null){
instance=new LazySingleton();
}
return instance;
}
// 双重检验锁
// 下面代码看是完美,但是JVM编译器存在指令重排的优化(有坑)
public static LazySingleton getInstance() {
if (instance==null){
synchronized (LazySingleton.class){
if (instance==null){
instance=new LazySingleton();
}
}
}
return instance;
}
}
饿汉模式
类加载的 初始化阶段就完成了 实例的初始化 。本质上就是借助于jvm 类加载机制,保证实例的唯一性(初始化过程只会执行一次)及线程安全(JVM以同步的形式来完成类加载的整个过程)。
类加载过程
1,加载二进制数据到内存中, 生成对应的Class数据结构,
2,连接: a. 验证, b.准备(给类的静态成员变量赋默认值),c.解析
3,初始化: 给类的静态变量赋初值
只有在真正使用对应的类时,才会触发初始化 如( 当前类是启动类即 main函数所在类,直接进行new 操作,访问静态属性、访问静态方 法,用反射访问类,初始化一个类的子类等.)
class HungrySingleton{
private static HungrySingleton instance=new HungrySingleton();
//让构造函数为 private,这样该类就不会被实例化
private HungrySingleton(){
}
public static HungrySingleton getInstance() {
return instance;
}
}
静态内部类
1).本质上是利用类的加载机制来保证线程安全
2).只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一 种形式。
class InnerClassSingleton{
private static class InnerClassHolder{
private static InnerClassSingleton instance= new InnerClassSingleton();
}
private InnerClassSingleton(){
}
public static InnerClassSingleton getInstance(){
return InnerClassHolder.instance;
}
}
反射攻击实例
Constructor<InnerClassSingleton> declaredConstructor=InnerClassSingleton.c lass.getDeclaredConstructor();
declaredConstructor.setAccessible( true );
InnerClassSingleton innerClassSingleton=declaredConstructor.newInstance();
InnerClassSingleton instance=InnerClassSingleton.getInstance();
System.out.println(innerClassSingleton==instance);
静态内部类防止反射破坏
class InnerClassSingleton {
private static class InnerClassHolder{
private static InnerClassSingleton instance= new InnerClassSingleton();
}
private InnerClassSingleton(){
if (InnerClassHolder.instance!=null){
throw new RuntimeException( " 单例不允许多个实例 " );
}
}
public static InnerClassSingleton getInstance(){
return InnerClassHolder.instance;
}
}
枚举类型
1)天然不支持反射创建对应的实例,且有自己的反序列化机制
2)利用类加载机制保证线程安全
public enum EnumSingleton {
INSTANCE;
public void print(){
System.out.println(this.hashCode());
}
}
序列化
1)可以利用 指定方法来替换从反序列化流中的数据 如下 1
ANY‐ACCESS‐MODIFIER Object readResolve() throws ObjectStreamException;
class InnerClassSingleton implements Serializable{
static final long serialVersionUID = 42L;
private static class InnerClassHolder{
private static InnerClassSingleton instance= new InnerClassSingleton();
}
private InnerClassSingleton(){
if (InnerClassHolder.instance!=null){
throw new RuntimeException( " 单例不允许多个实例 " );
}
}
public static InnerClassSingleton getInstance(){
return InnerClassHolder.instance;
}
Object readResolve() throws ObjectStreamException{
return InnerClassHolder.instance;
}
}