java有23中设计模式
设计模式分为三大类:
创建型模式,共五种:
工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:
适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:
策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
1、单例模式:
什么叫单例模式?
指一个类只有一个实例,且该类能自行创建这个实例的一种模式。
例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪 费,或出现各个窗口显示内容的不一致等错误。
在计算机系统中,还有 Windows 的回收站、操作系统中的文件系统、多线程中的线程池、显卡的驱动程序对 象、打印机的后台处理服务都是单例模式
单例模式有什么特点?
1.单例类只有一个实例对象;
2.该单例对象必须由单例类自行创建;
3.单例类对外提供一个访问该单例的全局访问点;
主要:
饿汉式(线程安全,调用效率高,但是不能延时加载)
懒汉式(线程安全,调用效率不高,但是可以延时加载)
其他:
双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)
静态内部类式(线程安全,调用效率高。但是可以延时加载)
枚举单例(线程安全,调用效率高,不能延时加载)
2、工厂模式:
简单工厂模式:用来生产同一等级结构中的任意产品(对已有产品新增功能,需要修改源代码)
虽然能通过工厂来创建对象,但是违反了开闭原则。一旦增加功能需要在原有基础上修改代码。
工厂方法模式:用来生产同一等级结构中的固定产品(支持增加任意产品,不用修改源代码)
将工厂类调整为工厂接口,需要什么类型的工厂就使用该类实现该工厂,创建相应的产品。
3.观察者模式:
也叫(发布-订阅模式)定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题 对象,这个主题对象在状态发生变化时,会通知所有观察者对象。使它们能够自动更新自己。
例如:发广播,游戏中大喇叭,群聊
jdk中提供了抽象主题和抽象观察者的接口,我们可以使用这两个接口来方便的定义自己的观察者模式
4.代理模式:
为其他对象提供一种代理以便控制对这个对象的访问。
可以详细控制访问某个类(对象)的方法,在调用这个方法前作的前置处理(统一的流程代码放到代理 中 处理)。调用这个方法后做后置处理。
例如:明星的经纪人,租房的中介等等都是代理
代理模式分类:
1.静态代理(静态定义代理类,我们自己静态定义的代理类。比如我们自己定义一个明星的经纪人类)
2.动态代理(通过程序动态生成代理类,该代理类不是我们自己定义的。而是由程序自动生成)比较重要!!
相关代码实现
面试官答:那你写一个线程安全的吧。
1.饿汉模式,线程安全
public class Singleton {
private Singleton(){};
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
}
将需要获取的 instance 实例在类加载的时候就实例化完毕,由于类的加载机制,此时静态变量的实例化只会执行一遍,所以可以保证此实例变量的唯一性。但是此时不具有延迟加载的特性。
面试官又问:线程安全还有其他写法吗?
我答:还可以使用 Synchronized 来修饰 get 实例的方法(面试官没让我写这个,但是我们还是给出懒汉模式的线程安全和不安全写法)。
懒汉模式,线程不安全
public class Singleton {
private Singleton(){};
private static Singleton instance;
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
原来:懒汉模式指的是这个单例类在用的时候会延迟加载,但是这个单例类是线程不安全的,在多线程环境下很容易出现多个实例的情况。
懒汉模式,线程安全
public class Singleton {
private Singleton(){};
private static Singleton instance;
public synchronized static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
原理:只要在上面的线程不安全模式下进行简单的修改就可以编程线程安全的,将 getInstance 方法用 Synchronized 修饰,确保同一个时刻只能有一个线程访问此方法。
面试官问:(Synchronized 的写法明显不是他想要考的点,他直接问我)
双重校验锁的写法了解吗?
我答:了解。
双重校验锁
public class Singleton {
private Singleton(){};
private volatile static Singleton instance;
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
原理:双重校验锁解决了饿汉模式下每次都要获取 Synchronized 同步锁的问题。第一次校验,会检查变量是否已经被实例化,如果实例化了就不去争取锁了,直接返回。如果没有第二次校验,可能会出现两个线程同时读到实例为 null 的情况。此时第二校验就需要加锁区判断实例是否为 null 了。
volatile 的作用是防止 JVM 指令重排导致出现错误,在某个线程创建单例对象时,在构造方法被调用前,就为该对象分配了内存空间并设置了默认值,此时就可以将分配的内存地址赋值给 instance 字段了,但是它可能还没有被初始化,此时另一个对象来调用 getInstance,取到的就是状态不正确的对象,volatile 能够防止指令重排序。
在我写出双重校验锁的写法后,面试官很满意,但是这还没有完,这个写法衍生出了很多考点。
面试官问:如果不加第二次校验会出什么问题?
我答:多线程环境下获取对象可能会出现空指针异常。
面试官问:volatile 关键字在这里起到了什么作用?
我答:(volatile 关键字是多线程最普遍的一个考点,它能防止指令重排序和可见性)这里主要是防止指令重排序,防止其中一个线程在初始化这个对象没结束时另一个线程使用这个对象。
这个题不但考了设计模式,还衍生出了许多多线程方面的面试题。下面给出其他几种写法。
静态内部类
public class Singleton {
private Singleton(){};
private static class SingleHolder{
private static Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return SingleHolder.instance;
}
}
原理:使用到了静态内部类的特性,只有使用到 instance 实例的时候才会被初始化,同时用到了静态变量的实例化机制确保全局只有一个实例,性能比较好。
枚举模式
public enum Singleton {
INSTANCE;
public Singleton getInstance(){
return INSTANCE;
}
}
枚举模式被认为是最安全最为有效的单例模式解决方案,解决了其他释放方式通过反射方式获取构造器、序列化等缺陷,它是在 JVM 层面实现的线程安全。
饿汉模式,变种
public class Singleton {
private Singleton(){};
private static Singleton instance = null;
static {
instance = new Singleton();
}
public static Singleton getInstance(){
return instance;
}
}