以下是对几个设计模式的一些总结分析~
单例模式
单例模式是一种对象创建模式,用于生产一个对象的实例,它可以确保系统中一个类只产生一个实例,这样做有两个好处:
1.对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销。
2.由于new操作的次数减少,所以系统内存的使用评率也会降低,这将减少GC压力,缩短GC停顿时间。
由于以上两点可知单例模式的使用对于系统的关键组件和频繁使用的对象来说是可以有效的改善系统的性能的。
单例的核心是通过一个方法返回唯一的一个对象实例,首先单例类必须有一个private访问级别的构造函数,因为,只有这样,才能保证单例不会再系统中的其他代码内被实例化,
(任务管理器就是单例模式的鲜明栗子哦~)
spring中单例模式的应用:
spring 中加载单例的过程都是在BeanFactory接口中定义的getBean()这个方法中定义的,实现默认是在AbstractBeanFactory中,主要代码功能有两点:
- 从缓存中获取单例bean
- 从bean的实例中获取对象
单例模式一般来说有两种:
(1)懒汉模式
即一开始就实例化单例对象,需要使用时直接通过函数获取对象即可
代码实现:
package com.lyq;
/**
* 单例模式--饿汉单例
*/
public class Singleton {
private static Singleton singleton=new Singleton();
private Singleton()
{}
public Singleton getInstance()
{
return singleton;
}
}
(2)饿汉模式
在需要使用单例对象时,再去调用构造函数进行实例化
代码实现:
package com.lyq;
/**
* 懒汉单例模式-加锁双重判断
*/
public class Singleton1 {
private volatile Singleton1 singleton = null;
private Singleton1() {
}
public Singleton1 getSingleton() {
if (singleton == null) {//1
//添加同步语句块
synchronized (Singleton.class) {//2
if (singleton == null) {//3
singleton = new Singleton1();
}
}
}
return singleton;
}
}
此处对于懒汉单例模式中的判断和加锁加以解释:
对于标记1处:
若是没有1,那么无法保证只有一个单例对象
对于标记2处:
加入只有1,只能应用于单线程的情况,而没有2的话,对于多线程会破坏单例情况
举个栗子:现有两个线程A,B,线程A想要调用getSingleton()函数获取到单例对象
但是,在执行完1之后,线程切换到B,B到1处,因为A线程还并未生成单例对象,所以此时单例对象为null,所以B线程就可以获取一个单例对象;紧接着,A线程继续往下执行,也可以获取到一个单例对象;
针对这种情况,在2处加上锁,每次只允许一个线程进入下面的实例构造区,对于上面所说的情况,在A进入2处的临界区之后,B线程必须队列中等待直到A线程释放锁之后,B线程才能进入临界区
对于标记3处:
那为什么还要在3处增加一个null值的判断呢?
因为在2加锁之后的那种情况中,A线程进入临界区,B线程处于等待状态
A继续获取到实例化对象,退出临界区,那么B就进入临界区,因为如果没有3
的判断,则B继续获取一个单例对象,就造成了有两个单例对象的情况了,所以在3处需要进行null的判断
那么,突然发现,有2,3的判断,就不需要1处的判断了?
(enummm,仿佛看起来是这个亚子滴)
但是,对于单线程的情况,每次都去加锁的话,对于资源的·浪费好像有点多诶
所以说,加上1对于单线程的情况可以减少资源的浪费呀~
还有一个疑惑,为什么要使用volatile关键字呢?
volatile除了可以保证多线程之间的修改可见性
还可以禁止指令重排的情况(那什么又是指令重排呢?
指令重排:(就拿这里new一个单例对象来举例吧~)
在new一个对象时,有以下几个步骤:
- 看class对象是否加载,如果没有就先加载class对象,
- 分配内存空间,初始化实例,
- 调用构造函数,
- 返回地址给引用。
而cpu为了优化程序,可能会进行指令重排序,打乱这3,4这几个步骤,导致实例内存还没分配,就被使用了。
再用线程A和线程B举例。线程A执行到new Singleton(),开始初始化实例对象,由于存在指令重排序,这次new操作,先把引用赋值了,还没有执行构造函数。这时时间片结束了,切换到线程B执行,线程B调用new Singleton()方法,发现引用不等于null,就直接返回引用地址了,然后线程B执行了一些操作,就可能导致线程B使用了还没有被初始化的变量。
加了volatile之后,就保证new 不会被指令重排序。
对于volatile的详细解析,请参考这位博主的文章
代理模式
(注:代理模式部分借鉴了这位博主的文章)
先小小总结几个点吧:
- 代理模式分为:静态代理 和 动态代理
他们都是通过实现接口来实现的,所以,要想通过实现类来实现的话就需要—cglib这个第三方库来实现了~ - 代理模式的本质目的:在不改变已有代码的基础上,对原有方法进行增强
需要注意的有下面几点:
- 用户只关心接口功能,而不在乎谁提供了功能。上图中接口是 Subject。
- 接口真正实现者是上图的 RealSubject,但是它不与用户直接接触,而是通过代理。
- 代理就是上图中的 Proxy,由于它实现了 Subject 接口,所以它能够直接与用户接触。
- 用户调用 Proxy 的时候,Proxy 内部调用了 RealSubject。所以,Proxy 是中介者,它可以增强 RealSubject 操作。
简单来说:
代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。代理类和被代理类需要实现同一个接口,或者是共同继承某个类。
形象化来描述:
就是对于相当于中介卖房的模式,中介与用户直接接触,房地产商只需要提供房源和房价等关于房子的信息,不需要关心如何去销售房子
而中介作为房地产商的代理人与买主直接接触,再这个接触的过程中,除了卖出房子,还可以收取到一定的中介费(这就是对卖房这件事的一个增强了相当于)
代理模式又分为动态代理和静态代理,主要区别在于:
静态代理是在代码运行之前就确定好需要进行代理的对象,即再卖房之前就预定好了中介人选
而动态代理则是在代码运行过程中来动态确定代理对象,即买主来到房源前看房子了,再找一个中介
静态代理模式
售房接口(代理类和真正的实现类都要实现这个接口)
public interface House{
void buy();
}
真正的购房人实现类
public class Buyer implements House{
@Override
public void buy() {
System.out.println("恭喜您购得超级商品房一套!");
}
}
中介代理类
public class Intermediary implements House{
Buyer buyer;
//构造函数调用时就获得购房对象,以便于后面可以实现购房操作
public Intermediary(Buyer buyer)
{
this.buyer=buyer;
}
@Override
public void buy() {
System.out.println("欢迎您来到小强中介!");
buyer.buy();
System.out.println("恭喜您通过小强中介购置炒鸡金屋一套,请支付中介费哦~!");
}
}
public class ProxyTest {
//测试类
public static void main(String[] args) {
Buyer buyer= new Buyer ();
Intermediary intermediary = new Intermediary (buyer);
//现在就通过小强中介购房了~
intermediary.buy();
}
}
动态代理模式
动态代理必须注意的一个类就是 Proxy类
动态代理的创建时通过Proxy 的静态方法 newProxyInstance 来实现的
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
它的 3 个参数意义:
- loader 被代理类的类加载器
- interfaces 被代理类实现的接口
- h 一个 InvocationHandler 对象
InvocationHandler
这是一个接口,每个代理的实例都有一个与之关联的 InvocationHandler 实现类,如果代理调用相关的方法,那么代理便会通知和转发给内部的InvocationHandler 实现类,由它来决定如何处理
具体的处理又是由invoke()方法来决定的,即相应的方法增强也是invoke方法中实现的
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
参数说明:
- proxy 代理对象
- method 代理对象调用的方法对象
- args 调用的方法中的参数
动态代理方法处理的本质:
Proxy 动态产生的代理会调用 InvocationHandler 实现类,所以 InvocationHandler 是实际执行者。
样例代码:
酒厂商接口
public interface SellWine {
void maiJiu();
}
具体实现类
即具体卖的酒:茅台酒作为实例(~顺带提一句,小强酒量还可)
public class MaotaiJiu implements SellWine {
@Override
public void maiJiu() {
System.out.println("小强推荐的茅台酒哦~");
}
}
卖酒的门店柜台来了~~~
public class Guitai implements InvocationHandler {
private Object pingpai;
public Guitai(Object pingpai) {
this.pingpai = pingpai;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
System.out.println("此次销售的柜台是: "+this.getClass().getSimpleName());
method.invoke(pingpai, args);
System.out.println("小强vip柜台销售一空了~");
return null;
}
}
接下来是小强的卖酒时刻~(go! go! go!)
public class Test {
public static void main(String[] args) {
MaotaiJiu maotaijiu = new MaotaiJiu();
InvocationHandler h1= new GuitaiA(maotaijiu);
//获取代理对象
SellWine dynamicProxy = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(),
MaotaiJiu.class.getInterfaces(), h1);
dynamicProxy.maiJiu();
}
}
此外继续深入一点点~
动态代理在newInstance的时候,为什么需要获取实现的接口的信息呢?
- 因为确实动态代理内部利用反射机制,通过获取到的接口的信息,实例化一个接口的实例对象,从而可以调用接口对应的方法
补充:
动态代理在Spring AOP的应用是一个很好的例子
Spring AOP 即面向切面编程(详细原理就不多说了)
常用用于日志记录,监控日志等方面
好处呢?就是可以减少业务逻辑各部分代码之间的耦合度
(后续待补充~)