SSM源码分析之23种设计模式(单例模式与原型模式)

前言

单例模式

单例模式(一个类模板,在整个系统执行过程中,只允许产生一个实例)应用广泛,主要 应用在:

  • 配置文件
  • Ioc容器
  • 日历
  • 工厂本身

单例模式:解决一个并发访问的时候线程安全问题,保证单例的技术方案有很多种

饿汉式单例

  1. 饿汉式
    在实例使用之前,不管你用不用,我都先new出来再说,避免了线程安全问题

饿汉式单例:

public class Hungry {

   //私有构造方法,防止外部new
   private Hungry(){}

   private static final Hungry hungry = new Hungry();

   public static Hungry getInstance(){
       return  hungry;
   }

}

我们来测试:

 public static void main(String[] args) {
       int count = 200;

       //发令枪,我就能想到运动员
       final CountDownLatch latch = new CountDownLatch(count);

       long start = System.currentTimeMillis();
       for (int i = 0; i < count;i ++) {
           new Thread(){
               @Override
               public void run() {
                   try{
                       try {
                           // 阻塞
                           // count = 0 就会释放所有的共享锁
                           // 万箭齐发
                           latch.await();
                       }catch(Exception e){
                           e.printStackTrace();
                       }
                       //必然会调用,可能会有很多线程同时去访问getInstance()
                       Object obj = Hungry.getInstance();
                       System.out.println(System.currentTimeMillis() + ":" + obj);
                   }catch (Exception e){
                       e.printStackTrace();
                   }
               }
           }.start(); //每循环一次,就启动一个线程,具有一定的随机性
           //每次启动一个线程,count --
           latch.countDown();
       }
       long end = System.currentTimeMillis();
       System.out.println("总耗时:" + (end - start));

   }

打印结果发现,无论怎么运行,程序始终拿到的是一个实例

懒汉式单例

  1. 懒汉式
    默认加载的时候不实例化,在需要用到这个实例的时候进行实例化(延时加载)
    懒汉式单例:
public class LazyOne {
    private LazyOne(){}
    //静态块,公共内存区域
    private static LazyOne lazy = null;
    
    public static LazyOne getInstance(){
        //调用方法之前,先判断
        //如果没有初始化,将其进行初始化,并且赋值
        //将该实例缓存好
        if(lazy == null){
            //两个线程都会进入这个if里面
            lazy = new LazyOne();
        }
        //如果已经初始化,直接返回之前已经保存好的结果
        return lazy;
    }
}

用1饿汉式的测试方法,只需要将

Object obj = Hungry.getInstance();

改成

 Object obj = LazyOne.getInstance();

打印结果发现存在不同实例,说明饿汉式单例是线程不安全!
怎么变为安全的呢!我们通常可以这样设计饿汉式单例(在获取实例方法上加上synchronized 锁)

public class LazyTwo {

    private LazyTwo(){}

    private static LazyTwo lazy = null;

    public static synchronized LazyTwo getInstance(){

        if(lazy == null){
            lazy = new LazyTwo();
        }
        return lazy;
    }
}

然后我们将测试工具类实例改为:

 Object obj = LazyTwo.getInstance();

运行发现,无论怎么运行,程序始终拿到的是一个实例,说明synchronized 锁是可以保证线程安全的!

但是synchronized 性能并不是最好的锁!
我们看这样的测试:

  public static void main(String[] args) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 200000000;i ++) {
            Object obj = LazyTwo.getInstance();
        }
        long end = System.currentTimeMillis();
        System.out.println("总耗时:" + (end - start));
    }

运行发现每次获取的时间性能较低
所以我们可以这样就LasyOne进行线程安全同步:

特点:在外部类被调用的时候内部类才会被加载
内部类一定是要在方法调用之前初始化
巧妙地避免了线程安全问题
这种形式兼顾饿汉式的内存浪费,也兼顾synchronized性能问题
完美地屏蔽了这两个缺点
//史上最牛B的单例模式的实现方式
public class LazyThree {

    private boolean initialized = false;

    //默认使用LazyThree的时候,会先初始化内部类
    //如果没使用的话,内部类是不加载的
    private LazyThree(){

        synchronized (LazyThree.class){
            if(initialized == false){
                initialized = !initialized;
            }else{
                throw new RuntimeException("单例已被侵犯");
            }
        }

    }

    //每一个关键字都不是多余的
    //static 是为了使单例的空间共享
    //保证这个方法不会被重写,重载
    public static final LazyThree getInstance(){
        //在返回结果以前,一定会先加载内部类
        return LazyHolder.LAZY;
    }

    //默认不加载
    private static class LazyHolder{
        private static final LazyThree LAZY = new LazyThree();
    }

}

我们来测试一下:

public static void main(String[] args) {

        try{

            //很无聊的情况下,进行破坏
            Class<?> clazz = LazyThree.class;

            //通过反射拿到私有的构造方法
            Constructor c = clazz.getDeclaredConstructor(null);
            //强制访问,强吻,不愿意也要吻
            c.setAccessible(true);

            //暴力初始化
            Object o1 = c.newInstance();

            //调用了两次构造方法,相当于new了两次
            //犯了原则性问题,
            Object o2 = c.newInstance();

            System.out.println(o1 == o2);
//            Object o2 = c.newInstance();

        }catch (Exception e){
            e.printStackTrace();
        }
    }

注册登记式单例

  1. 注册登记式
    每使用一次,都往一个固定的容器中去注册并且将使用过的对象进行缓存,下次取对象就直接从缓存中取值,以保证每次获取的都是同一个对象

IOC中的单例模式,就是典型的注册登记式单例
基于map形式的注册单例:

public class RegisterMap {

   private RegisterMap(){}

   private static Map<String,Object> register = new ConcurrentHashMap<String,Object>();

   public static RegisterMap getInstance(String name){
       if(name == null){
           name = RegisterMap.class.getName();
       }

       if(register.get(name) == null){
           try {
               register.put(name, new RegisterMap());
           }catch(Exception e){
               e.printStackTrace();
           }
       }
       return (RegisterMap)register.get(name);
   }

}

枚举式单例

  1. 枚举式
    使用常量值来保证对象的唯一,实际上就是注册登记式的一种
public enum RegiterEnum {
   INSTANCE,BLACK,WHITE;
   public void getInstance(){}

}

测试:

RegiterEnum.INSTANCE.getInstance();
RegiterEnum.BLACK.getInstance();
RegiterEnum.WHITE.getInstance();

序列化与反序列化单例

  1. 序列化与反序列化

重写readResolve()方法

//反序列化时导致单例破坏
public class Seriable implements Serializable {
   //序列化就是说把内存中的状态通过转换成字节码的形式
   //从而转换一个IO流,写入到其他地方(可以是磁盘、网络IO)
   //内存中状态给永久保存下来了

   //反序列化
   //讲已经持久化的字节码内容,转换为IO流
   //通过IO流的读取,进而将读取的内容转换为Java对象
   //在转换过程中会重新创建对象new

   public  final static Seriable INSTANCE = new Seriable();
   private Seriable(){}

   public static  Seriable getInstance(){
       return INSTANCE;
   }

   private  Object readResolve(){
       return  INSTANCE;
   }

}

测试:

public static void main(String[] args) {

        Seriable s1 = null;
        Seriable s2 = Seriable.getInstance();

        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream("Seriable.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s2);
            oos.flush();
            oos.close();


            FileInputStream fis = new FileInputStream("Seriable.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            s1 = (Seriable)ois.readObject();
            ois.close();

            System.out.println(s1);
            System.out.println(s2);
            System.out.println(s1 == s2);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

原型模式

定义:把对象中配置的依赖关系,在每次使用对象之前,都会创建一个新的对象,并且会将依赖关系完整的赋值给这个新创建的对象。

Tips:spring默认是单例模式

场景描述:

先了解一下:dto 、vo、entity(do)
dto vo entity使用场景-简书
dto vo entity使用场景-csdn

所以一般情况下就是:
entity:数据表映射的实体字段
vo:后台通常用vo包装类来接前端传给后台的字段
dto:处理entity和vo冲突的映射关系实体

复制:就是要把dto中每个属性的值赋值给vo中的每一个属性的值,属性名称相同,属性类型相同

Apache:反射去实现(原型模式)
jdk:clone()方法实现

浅复制

通过实现jdk的clone()方法

public class Prototype implements Cloneable {

    public String name;

    CloneTarget target = null;

通过子类来实现接口的clone()方法

public class CloneTarget extends Prototype {

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

}

我们测试一下:

public static void main(String[] args) {

        CloneTarget p = new CloneTarget();
        p.name = "Tom";
        p.target = new CloneTarget();
        System.out.println(p.target);

        try {
            CloneTarget obj =  (CloneTarget) p.clone();
            System.out.println(obj.target);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

打印结果,浅复制成功!(实际上复制的是栈的引用地址)

深复制

首先做一个猴子类:

public class Monkey {
    public int height;
    public int weight;
    public Date birthday;

}

再来一个齐天大圣:

public class QiTianDaSheng extends Monkey implements Cloneable,Serializable {

    public JinGuBang jinGuBang;

    public  QiTianDaSheng(){
        //只是初始化
        this.birthday = new Date();
        this.jinGuBang = new JinGuBang();
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return this.deepClone();
    }


    public Object deepClone(){
        try{

            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);

            QiTianDaSheng copy = (QiTianDaSheng)ois.readObject();
            copy.birthday = new Date();
            return copy;

        }catch (Exception e){
            e.printStackTrace();
            return null;
        }

    }


    public QiTianDaSheng copy(QiTianDaSheng target){

        QiTianDaSheng qiTianDaSheng = new QiTianDaSheng();
        qiTianDaSheng.height = target.height;
        qiTianDaSheng.weight = target.height;

        qiTianDaSheng.jinGuBang = new JinGuBang();
        qiTianDaSheng.jinGuBang.h = target.jinGuBang.h;
        qiTianDaSheng.jinGuBang.d = target.jinGuBang.d;

        qiTianDaSheng.birthday = new Date();
        return  qiTianDaSheng;
    }


}

再来一个金箍棒:

public class JinGuBang implements Serializable {
    public float h = 100;
    public float d = 10;

    public void big(){
        this.d *= 2;
        this.h *= 2;
    }

    public void small(){
        this.d /= 2;
        this.h /= 2;
    }
}

我们测试一下:

  public static void main(String[] args) {

        QiTianDaSheng q = new QiTianDaSheng();
        QiTianDaSheng n = q.copy(q);
        System.out.println(q.jinGuBang == n.jinGuBang);
    }

复制成功!

后记

tips: 我们知道,原型模式,可以通过实现序列化io流的形式复制,也可以通过反射,spring中的原型模式基本上都是通过反射实现。
本期代码:gitlab代码地址

发布了47 篇原创文章 · 获赞 5 · 访问量 1884

猜你喜欢

转载自blog.csdn.net/qq_34361283/article/details/103142179