写在后面

  • 记录学习设计模式的笔记
  • 进步对设计模式的灵活运用

学习地址

https://www.bilibili.com/vide...

https://www.bilibili.com/vide...

参考文章

http://c.biancheng.net/view/1...

我的项目源码
https://gitee.com/zhuang-kang/DesignPattern

4,创建者模式

创立型模式的次要关注点是“怎么创建对象?”,它的次要特点是“将对象的创立与应用拆散”。

这样能够升高零碎的耦合度,使用者不须要关注对象的创立细节。

创立型模式分为:

  • 单例模式
  • 工厂办法模式
  • 形象工程模式
  • 原型模式
  • 建造者模式

5,单例模式

5.1 单例模式的定义和特点

单例(Singleton)模式的定义: 指一个类只有一个实例,且该类能自行创立这个实例的一种模式。例如,Windows 中只能关上一个工作管理器,这样能够防止因关上多个工作管理器窗口而造成内存资源的节约,或呈现各个窗口显示内容的不统一等谬误。

在计算机系统中,还有 Windows 的回收站、操作系统中的文件系统、多线程中的线程池、显卡的驱动程序对象、打印机的后盾解决服务、应用程序的日志对象、数据库的连接池、网站的计数器、Web 利用的配置对象、应用程序中的对话框、零碎中的缓存等经常被设计成单例。

单例模式在现实生活中的利用也十分宽泛,例如公司 CEO、部门经理等都属于单例模型。J2EE 规范中的 ServletgContext 和 ServletContextConfig、Spring 框架利用中的 ApplicationContext、数据库中的连接池等也都是单例模式。

单例模式有 3 个特点:

  1. 单例类只有一个实例对象;
  2. 该单例对象必须由单例类自行创立;
  3. 单例类对外提供一个拜访该单例的全局拜访点。

单例模式的长处

  • 单例模式能够保障内存里只有一个实例,缩小了内存的开销。
  • 能够防止对资源的多重占用。
  • 单例模式设置全局拜访点,能够优化和共享资源的拜访。

单例模式的毛病:

  • 单例模式个别没有接口,扩大艰难。如果要扩大,则除了批改原来的代码,没有第二种路径,违反开闭准则。
  • 在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模仿生成一个新的对象。
  • 单例模式的性能代码通常写在一个类中,如果功能设计不合理,则很容易违反繁多职责准则。

5.2 单例模式的构造与实现

5.2.1 单例模式的构造

  1. 单例类:蕴含一个实例且能自行创立这个实例的类。
  2. 拜访类:应用单例的类。

5.2 代码实现

单例设计模式分类两种:

饿汉式:类加载就会导致该单实例对象被创立    懒汉式:类加载不会导致该单实例对象被创立,而是首次应用该对象时才会创立

饿汉式(动态变量)

package com.zhuang.singleton.type1;/** * @Classname SingletonTest01 * @Description  饿汉式(动态变量) * @Date 2021/3/17 9:26 * @Created by dell */public class SingletonTest01 {    public static void main(String[] args) {        Singleton instance = Singleton.getInstance();        Singleton instance2 = Singleton.getInstance();        //判断是否为单例        System.out.println(instance == instance2);        System.out.println("intstance的哈希值" + instance.hashCode());        System.out.println("intstance2的哈希值" + instance2.hashCode());    }}class Singleton {    //1.结构器私有化,内部能new、    private Singleton() {    }    //本类外部创建对象实例    private final static Singleton instance = new Singleton();    //对外部提供一个私有的静态方法    public static Singleton getInstance() {        return instance;    }}

<font color='red'>阐明:</font>

该形式在成员地位申明Singleton类型的动态变量,并创立Singleton类的对象instance。instance对象是随着类的加载而创立的。如果该对象足够大的话,而始终没有应用就会造成内存的节约。

动态代码块

package com.zhuang.singleton.type2;/** * @Classname SingletonTest02 * @Description  动态代码块 * @Date 2021/3/17 9:35 * @Created by dell */public class SingletonTest02 {    public static void main(String[] args) {        Singleton2 instance = Singleton2.getInstance();        Singleton2 instance2 = Singleton2.getInstance();        //判断是否为单例        System.out.println(instance == instance2);        System.out.println("intstance的哈希值" + instance.hashCode());        System.out.println("intstance2的哈希值" + instance2.hashCode());    }}class Singleton2 {    //1.结构器私有化,内部能new、    private Singleton2() {    }    //本类外部创建对象实例    private static Singleton2 instance;    /*    在动态代码块中创建对象     */    static {        instance = new Singleton2();    }    //对外部提供一个私有的静态方法    public static Singleton2 getInstance() {        return instance;    }}

<font color='red'>阐明:</font>

该形式在成员地位申明Singleton类型的动态变量,而对象的创立是在动态代码块中,也是对着类的加载而创立。所以和饿汉式的形式1基本上一样,**当然该形式也存在内存节约问题。**

懒汉式 线程不平安

package com.zhuang.singleton.type3;/** * @Classname SingletonTest03 * @Description  懒汉式 线程不平安 * @Date 2021/3/17 9:39 * @Created by dell */public class SingletonTest03 {    public static void main(String[] args) {        System.out.println("懒汉式,线程不平安!!!");        Singleton instance = Singleton.getInstance();        Singleton instance2 = Singleton.getInstance();        //判断是否为单例        System.out.println(instance == instance2);        System.out.println("intstance的哈希值" + instance.hashCode());        System.out.println("intstance2的哈希值" + instance2.hashCode());    }}class Singleton {    private static Singleton instance;    private Singleton() {    }    //提供一个动态的私有办法 当应用到该办法时,才去创立instance    public static Singleton getInstance() {        if (instance == null) {            instance = new Singleton();        }        return instance;    }}

<font color='red'>阐明:</font>

从下面代码咱们能够看出该形式在成员地位申明Singleton类型的动态变量,并没有进行对象的赋值操作,那么什么时候赋值的呢?当调用getInstance()办法获取Singleton类的对象的时候才创立Singleton类的对象,这样就实现了懒加载的成果。然而,如果是多线程环境,会呈现线程平安问题。

懒汉式(线程平安 , 同步办法)

package com.zhuang.singleton.type4;/** * @Classname SingletonTest04 * @Description  懒汉式(线程平安 , 同步办法) * @Date 2021/3/17 9:46 * @Created by dell */public class SingletonTest04 {    public static void main(String[] args) {        System.out.println("懒汉式,线程平安!!!");        Singleton instance = Singleton.getInstance();        Singleton instance2 = Singleton.getInstance();        //判断是否为单例        System.out.println(instance == instance2);        System.out.println("intstance的哈希值" + instance.hashCode());        System.out.println("intstance2的哈希值" + instance2.hashCode());    }}class Singleton {    private static Singleton instance;    private Singleton() {    }    //提供一个动态的私有办法,退出同步解决的代码,解决线程平安问题    public static synchronized Singleton getInstance() {        if (instance == null) {            instance = new Singleton();        }        return instance;    }}

<font color='red'>阐明:</font>

该形式也实现了懒加载成果,同时又解决了线程平安问题。然而在getInstance()办法上增加了synchronized关键字,导致该办法的执行成果特地低。从下面代码咱们能够看出,其实就是在初始化instance的时候才会呈现线程平安问题,一旦初始化实现就不存在了。

懒汉式(线程平安 , 同步代码块)

package com.zhuang.singleton.type5;/** * @Classname SingletonTest05 * @Description  懒汉式(线程平安 , 同步代码块) * @Date 2021/3/17 9:50 * @Created by dell */public class SingletonTest05 {    public static void main(String[] args) {        System.out.println("懒汉式,线程平安!,同步代码块");        Singleton instance = Singleton.getInstance();        Singleton instance2 = Singleton.getInstance();        //判断是否为单例        System.out.println(instance == instance2);        System.out.println("intstance的哈希值" + instance.hashCode());        System.out.println("intstance2的哈希值" + instance2.hashCode());    }}class Singleton {    private static Singleton instance;    private Singleton() {    }    //提供一个动态的私有办法,退出同步解决的代码,解决线程平安问题    public static synchronized Singleton getInstance() {        if (instance == null) {            synchronized (Singleton.class) {                instance = new Singleton();            }        }        return instance;    }}

双重查看锁模式是一种十分好的单例实现模式,解决了单例、性能、线程平安问题,下面的双重检测锁模式看上去白璧无瑕,其实是存在问题,在多线程的状况下,可能会呈现空指针问题,呈现问题的起因是JVM在实例化对象的时候会进行优化和指令重排序操作。

要解决双重查看锁模式带来空指针异样的问题,只须要应用 volatile 关键字, volatile 关键字能够保障可见性和有序性。

package com.zhuang.singleton.type6;/** * @Classname SingletonTest06 * @Description  双重查看,举荐应用 * @Date 2021/3/17 9:54 * @Created by dell */public class SingletonTest06 {    public static void main(String[] args) {        System.out.println("懒汉式,双重查看,举荐应用");        Singleton instance = Singleton.getInstance();        Singleton instance2 = Singleton.getInstance();        //判断是否为单例        System.out.println(instance == instance2);        System.out.println("intstance的哈希值" + instance.hashCode());        System.out.println("intstance2的哈希值" + instance2.hashCode());    }}class Singleton {    private static volatile Singleton instance;    private Singleton() {    }    //提供一个动态的私有办法,退出双重查看代码,退出同步解决的代码,解决懒加载的问题    //保障效率。举荐应用    public static synchronized Singleton getInstance() {        if (instance == null) {            synchronized (Singleton.class) {                instance = new Singleton();            }        }        return instance;    }}

<font color="red">小结:</font>

增加 volatile 关键字之后的双重查看锁模式是一种比拟好的单例实现模式,可能保障在多线程的状况下线程平安也不会有性能问题。

动态外部类实现单例模式!

package com.zhuang.singleton.type7;/** * @Classname SingletonTest07 * @Description  动态外部类实现单例模式! * @Date 2021/3/17 9:59 * @Created by dell */public class SingletonTest07 {    public static void main(String[] args) {        System.out.println("动态外部类实现单例模式");        Singleton instance = Singleton.getInstance();        Singleton instance2 = Singleton.getInstance();        //判断是否为单例        System.out.println(instance == instance2);        System.out.println("intstance的哈希值" + instance.hashCode());        System.out.println("intstance2的哈希值" + instance2.hashCode());    }}class Singleton {    private static Singleton instance;    private Singleton() {    }    //写一个动态外部类,该类中有个动态属性,Singleton    public static class SingletonInstance {        private static final Singleton INSTANCE = new Singleton();    }    //提供一个动态的私有办法,间接返回SingletonInstance.INSTANCE;    public static synchronized Singleton getInstance() {        return SingletonInstance.INSTANCE;    }}

<font color='red'>阐明:</font>

第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder

并初始化INSTANCE,这样不仅能确保线程平安,也能保障 Singleton 类的唯一性。

<font color="red">小结:</font>

动态外部类单例模式是一种优良的单例模式,是开源我的项目中比拟罕用的一种单例模式。在没有加任何锁的状况下,保障了多线程下的平安,并且没有任何性能影响和空间的节约。

枚举的形式实现单例模式

package com.zhuang.singleton.type8;/** * @Classname SingletonTest08 * @Description  枚举的形式实现单例模式 * @Date 2021/3/17 10:06 * @Created by dell */public class SingletonTest08 {    public static void main(String[] args) {        System.out.println("枚举的形式实现单例模式,举荐应用");        Singleton instance = Singleton.INSTANCE;        Singleton instance2 = Singleton.INSTANCE;        System.out.println(instance == instance2);        //判断是否为单例        System.out.println(instance == instance2);        System.out.println("intstance的哈希值" + instance.hashCode());        System.out.println("intstance2的哈希值" + instance2.hashCode());    }}/*枚举 */enum Singleton {    INSTANCE;//属性    public void method() {        System.out.println("method()办法被调用...");    }}

阐明:

枚举形式属于恶汉式形式。

5.3 单例模式的利用场景

  • 须要频繁创立的一些类,应用单例能够升高零碎的内存压力,缩小 GC。
  • 某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
  • 某些类创立实例时占用资源较多,或实例化耗时较长,且常常应用。
  • 某类须要频繁实例化,而创立的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
  • 频繁拜访数据库或文件的对象。
  • 对于一些管制硬件级别的操作,或者从零碎上来讲该当是繁多管制逻辑的操作,如果有多个实例,则零碎会齐全乱套。
  • 当对象须要被共享的场合。因为单例模式只容许创立一个对象,共享该对象能够节俭内存,并放慢对象访问速度。如 Web 中的配置对象、数据库的连接池等。

5.4 存在的问题

毁坏单例模式:

使下面定义的单例类(Singleton)能够创立多个对象,枚举形式除外。有两种形式,别离是序列化和反射。

  • 序列化反序列化

    Singleton类:

    public class Singleton implements Serializable {    //公有构造方法    private Singleton() {}    private static class SingletonHolder {        private static final Singleton INSTANCE = new Singleton();    }    //对外提供静态方法获取该对象    public static Singleton getInstance() {        return SingletonHolder.INSTANCE;    }}

Test类:

public class Test {    public static void main(String[] args) throws Exception {        //往文件中写对象        //writeObject2File();        //从文件中读取对象        Singleton s1 = readObjectFromFile();        Singleton s2 = readObjectFromFile();        //判断两个反序列化后的对象是否是同一个对象        System.out.println(s1 == s2);    }    private static Singleton readObjectFromFile() throws Exception {        //创建对象输出流对象        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\dell\\Desktop\\a.txt"));        //第一个读取Singleton对象        Singleton instance = (Singleton) ois.readObject();        return instance;    }    public static void writeObject2File() throws Exception {        //获取Singleton类的对象        Singleton instance = Singleton.getInstance();        //创建对象输入流        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\dell\\Desktop\\a.txt"));        //将instance对象写出到文件中        oos.writeObject(instance);    }}
下面代码运行后果是false,表明序列化和反序列化曾经毁坏了单例设计模式。
  • 反射

    Singleton类:

    public class Singleton {    //公有构造方法    private Singleton() {}        private static volatile Singleton instance;    //对外提供静态方法获取该对象    public static Singleton getInstance() {        if(instance != null) {            return instance;        }        synchronized (Singleton.class) {            if(instance != null) {                return instance;            }            instance = new Singleton();            return instance;        }    }}

Test类:

public class Test {    public static void main(String[] args) throws Exception {        //获取Singleton类的字节码对象        Class clazz = Singleton.class;        //获取Singleton类的公有无参构造方法对象        Constructor constructor = clazz.getDeclaredConstructor();        //勾销拜访查看        constructor.setAccessible(true);        //创立Singleton类的对象s1        Singleton s1 = (Singleton) constructor.newInstance();        //创立Singleton类的对象s2        Singleton s2 = (Singleton) constructor.newInstance();        //判断通过反射创立的两个Singleton对象是否是同一个对象        System.out.println(s1 == s2);    }}

下面代码运行后果是false,表明序列化和反序列化曾经毁坏了单例设计模式

<font color="red">留神:</font>枚举形式不会呈现这两个问题。

问题的解决

  • 序列化、反序列形式毁坏单例模式的解决办法

    在Singleton类中增加readResolve()办法,在反序列化时被反射调用,如果定义了这个办法,就返回这个办法的值,如果没有定义,则返回新new进去的对象。

    Singleton类:

    public class Singleton implements Serializable {    //公有构造方法    private Singleton() {}    private static class SingletonHolder {        private static final Singleton INSTANCE = new Singleton();    }    //对外提供静态方法获取该对象    public static Singleton getInstance() {        return SingletonHolder.INSTANCE;    }        /**     * 上面是为了解决序列化反序列化破解单例模式     */    private Object readResolve() {        return SingletonHolder.INSTANCE;    }}

源码解析:

ObjectInputStream类

public final Object readObject() throws IOException, ClassNotFoundException{    ...    // if nested read, passHandle contains handle of enclosing object    int outerHandle = passHandle;    try {        Object obj = readObject0(false);//重点查看readObject0办法    .....}    private Object readObject0(boolean unshared) throws IOException {    ...    try {        switch (tc) {            ...            case TC_OBJECT:                return checkResolve(readOrdinaryObject(unshared));//重点查看readOrdinaryObject办法            ...        }    } finally {        depth--;        bin.setBlockDataMode(oldMode);    }    }    private Object readOrdinaryObject(boolean unshared) throws IOException {    ...    //isInstantiable 返回true,执行 desc.newInstance(),通过反射创立新的单例类,    obj = desc.isInstantiable() ? desc.newInstance() : null;     ...    // 在Singleton类中增加 readResolve 办法后 desc.hasReadResolveMethod() 办法执行后果为true    if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) {        // 通过反射调用 Singleton 类中的 readResolve 办法,将返回值赋值给rep变量        // 这样屡次调用ObjectInputStream类中的readObject办法,继而就会调用咱们定义的readResolve办法,所以返回的是同一个对象。        Object rep = desc.invokeReadResolve(obj);         ...    }    return obj;}
  • 反射形式破解单例的解决办法

    public class Singleton {    //公有构造方法    private Singleton() {        /*           反射破解单例模式须要增加的代码        */        if(instance != null) {            throw new RuntimeException();        }    }        private static volatile Singleton instance;    //对外提供静态方法获取该对象    public static Singleton getInstance() {        if(instance != null) {            return instance;        }        synchronized (Singleton.class) {            if(instance != null) {                return instance;            }            instance = new Singleton();            return instance;        }    }}

<font color="red">阐明:</font>

  这种形式比拟好了解。当通过反射形式调用构造方法进行创立创立时,间接抛异样。不运行此中操作。

5.5 JDK源码解析-Runtime类

Runtime类就是应用的单例设计模式。

  1. 通过源代码查看应用的是哪儿种单例模式

    public class Runtime {    private static Runtime currentRuntime = new Runtime();    /**     * Returns the runtime object associated with the current Java application.     * Most of the methods of class <code>Runtime</code> are instance     * methods and must be invoked with respect to the current runtime object.     *     * @return  the <code>Runtime</code> object associated with the current     *          Java application.     */    public static Runtime getRuntime() {        return currentRuntime;    }    /** Don't let anyone else instantiate this class */    private Runtime() {}    ...}

从下面源代码中能够看出Runtime类应用的是恶汉式(动态属性)形式来实现单例模式的。

  1. 应用Runtime类中的办法

    public class RuntimeDemo {    public static void main(String[] args) throws IOException {        //获取Runtime类对象        Runtime runtime = Runtime.getRuntime();        //返回 Java 虚拟机中的内存总量。        System.out.println(runtime.totalMemory());        //返回 Java 虚拟机试图应用的最大内存量。        System.out.println(runtime.maxMemory());        //创立一个新的过程执行指定的字符串命令,返回过程对象        Process process = runtime.exec("ipconfig");        //获取命令执行后的后果,通过输出流获取        InputStream inputStream = process.getInputStream();        byte[] arr = new byte[1024 * 1024* 100];        int b = inputStream.read(arr);        System.out.println(new String(arr,0,b,"gbk"));    }}

写在最初

  • 如果我的文章对你有用,请给我点个,感激你!
  • 有问题,欢送在评论区指出!