设计模式

设计模式分类

  1. 创建型模式:
    单例模式、工厂模式、抽象工程模式、建造者模式、原型模式
  2. 结构型模式
    适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
  3. 行为型模式
    模板方法模式、命令模式、迭代器模式、观察者模式、终结者模式、备忘录模式、揭示其模式、状态模式、策略模式、职责链模式、访问者模式

单例模式

  1. 核心作用
    保证一个类只有一个实例,并且提供一个访问该实力的全局访问点
  2. 创建场景
    Windows的任务管理器
    Windows的回收站
    项目中读取配置文件的类
    网站的计数器
    应用程序的日志应用
    数据库连接池
    操作系统文件系统
    Application(servlet编程中会涉及)
    Spring中,每个Bean默认是单例模式,Spring容器可以管理
    servlet编程中,servlet也是单例
    Spring MVC框架/struts1框架中,控制器对象也是单例
  3. 优点
    由于单例模式只生成一个实例,减少了系统性能的开销,当一个对象的产生需要比较多的资源时,可以通过应用启动时直接产生一个实例对象,然后永久驻留内存的方式来解决
    单例模式可以在系统设置全举办的访问点,优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理
  4. 常见的5中单例模式实现方式
    主要:
    饿汉式(线程安全,调用效率高。但是,不能延时加载。)
    懒汉式(线程安全,调用效率不高。但是,可以延时加载。)
    其他:
    双重检测锁式(由于JVM底层内部模型原因,偶尔会出现问题。不建议使用)
    静态内部类式(线程安全,调用效率高。可以延时加载)
    枚举单例(线程安全,调用效率高,但不能延时加载,并且可以天然的防止反射和反序列化漏洞!)
饿汉式(单例对象立即加载)

特点:线程安全,效率高,但不能延时加载
三点注意:

  1. 构造器私有
  2. 静态属性直接new,因为是饿汉式非常饿,刚开始就直接new
  3. 获取单例对象的静态方法 不需要synchroniezd
public class Demo02 {
    private static Demo02 instance = new Demo02(); //饿汉式非常饿,上来就new,因为立即加载了,就没有延时加载的优势
    private Demo02() {};
    //这里不用设置线程同步 因为类加载的时候,是一个天然的线程安全模式,所以线程安全,不需要同步,显然效率高
    public static Demo02 getInstance() {
        return demo02;
    }
}
懒汉式(单例对象 延时加载)

特点:线程安全,效率不高,可以延时加载(这就是懒)
三点注意:

  1. 构造器私有
  2. 静态属性不初始化
  3. 获取单例对象的方法 需要加synchronized
public class Demo03 {
    private static Demo03 instance; //懒汉式,懒所以不立即加载
    private Demo03() {};
  
    //需要时才new,资源利用率高,但是并发效率不高,用了synchroniezd
    public synchronized static Demo03 getInstance() {
        if (instance == null) {
            instance = new Demo03();
        }
        return instance;
    }
}
双重检测锁

因为编译器优化和jvm内存模型问题,不建议使用,在 Java 5.0 之后,使用 volatile 来修饰 singleInstance 实例,就不会产生指令重排序的情况,这样 DCL 也就可以正常工作了。
但因为有了更加方便与安全的替代方式,DCL 也没有什么特别的优势,便被废弃了。

public class Demo04 {
    private static Demo04 instance;
    private Demo04() {}
    public static Demo04 getInstance() {
        if (instance == null) {
            Demo04 demo04;
            synchronized (Demo04.class) {
                demo04 = instance;
                if (demo04 == null) {
                    synchronized (Demo04.class) {
                        demo04 = new Demo04();
                    }
                }
                instance = demo04;
            }
        }
        return instance;
    }
}

上面代码是视频里面的 因为没有讲解我看不懂
然后用volatile实现的双重检测锁

public class Demo05 {
    //加上volatile可以解决编译器的优化问题和cpu缓存
    private static volatile Demo05 instance;
    private Demo05() {}
    public static Demo05 getInstance() {
        if (instance == null) {
            synchronized (Demo05.class) {
                if (instance == null) {
                    instance = new Demo05();
                }
            }
        }
        return instance;
    }
}
静态内部类(也是一种懒加载方式)

特点:线程安全,调用效率高。可以延时加载
要点:
构造器私有
外部类没有static属性,就不会像饿汉式那样立即加载对象
只有真正调用getInstance(),才会加载静态内部类。加载类时是线程安全的。instance是static final类型,保证内存中只有一个实例存在,而且只能被赋值一次,从而保证了线程安全性。
兼备了并发高效调用和延时加载的优势

//加载类是不回去加载静态内部类,只有调用方法时才会加载,所以是懒加载,也就能延时加载
public class Demo06 {
    private Demo06() {}
    private static class ClassInstance {
        private static final Demo06 instance = new Demo06();
    }
    public static Demo06 getInstance() {
        return ClassInstance.instance;
    }
}
枚举实现

优点:实现简单,枚举本身就是单例模式,由JVM从根本上提供保障,避免通过反射和反序列化的漏洞
缺点:无延迟加载

//枚举类
public enum Demo07 {
    //枚举元素,本身就是单例,可以直接类名调用
    INSTANCE;
    //枚举类还有可以自己的操作
    public void operationMethod() {
        //代码
    }
}
如何防止反射和反序列漏洞
  1. 解决反射
    在私有构造方法中加入if (instance != null) {throw new RuntimeException}
public class Demo02 {
    private static Demo02 instance = new Demo02(); //饿汉式非常饿,上来就new
    private Demo02() {
        if (instance != null) {
            throw new RuntimeException();
        }
    };
    //这里不用设置线程同步 因为类加载的时候,是一个天然的线程安全模式
    public static Demo02 getInstance() {
        return instance;
    }
}

class Main01 {
    public static void main(String[] args) {
        Demo02 demo02_01 = Demo02.getInstance();
        Demo02 demo02_02 = Demo02.getInstance();
        System.out.println(demo02_01);
        System.out.println(demo02_02);
        try {
            Class<Demo02> cla = (Class<Demo02>)Class.forName("单例设计模式.Demo02");
            Constructor<Demo02> constructor = cla.getDeclaredConstructor(null);
            constructor.setAccessible(true);
            Demo02 demo02_03 = constructor.newInstance();
            Demo02 demo02_04 = constructor.newInstance();
            System.out.println(demo02_03);
            System.out.println(demo02_04);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
  1. 反序列化时需要一个方法 private Object readResolve() throws ObjectStreamException {return instance}
public class Demo03 implements Serializable {
    private static Demo03 instance; //懒汉式,懒所以不立即加载
    private Demo03() {};

    //需要时才new,资源利用率高,但是并发效率不高,用了synchroniezd
    public synchronized static Demo03 getInstance() {
        if (instance == null) {
            instance = new Demo03();
        }
        return instance;
    }

    private Object readResolve() throws ObjectStreamException {
        return instance;
    }
}

class Main02 {
    public static void main(String[] args) {
        Demo03 demo03_01 = Demo03.getInstance();
        Demo03 demo03_02 = Demo03.getInstance();
        System.out.println(demo03_01);
        System.out.println(demo03_02);
        try {
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("C:/users/night/desktop/a.txt"));
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("C:/users/night/desktop/a.txt"));
            objectOutputStream.writeObject(demo03_01);
            Demo03 demo03_03 = (Demo03) objectInputStream.readObject();
            System.out.println(demo03_03);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
多线程模式下测试单例模式各个方法的效率

用countDownLatch测试方法的效率

public class Demo08 {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        int threadNum = 10;
        final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
        for (int i = 0; i < threadNum; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000000; j++) {
                    Object instance = Demo07.INSTANCE;
                }
                countDownLatch.countDown();
            }).start();
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }
}

如何选用?

单例对象 占用资源少,不需要延时加载
枚举式好于饿汉式
单例对象 占用资源大,需要延时记载
静态内部类好于懒汉式