第三节 常用设计模式之单例模式

亮子 2021-09-10 18:25:20 10793 0 0 0

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

注意:

  • 单例类只能有一个实例。
  • 单例类必须自己创建自己的唯一实例。
  • 单例类必须给所有其他对象提供这一实例。

单例模式的优点:

  • 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
  • 避免对资源的多重占用(比如写文件操作)。

单例模式的缺点:

没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

1)、懒汉式,线程不安全

优点:懒加载启动快,资源占用小,使用时才实例化,无锁。

缺点:非线程安全。

/**
 * @program: server-java-demo
 * @description: 单例模式:懒汉,线程不安全
 * @author: 亮子说编程
 * @create: 2020-10-15 09:06
 **/
public class Singleton01 {
    private static Singleton01 instance;

    private Singleton01() {
    }

    public static Singleton01 getInstance() {
        if (instance == null) {
            instance = new Singleton01();
        }
        return instance;
    }
}

这种写法lazy loading很明显,但是致命的是在多线程不能正常工作。

2)、懒汉式,线程安全

优点:同上,但加锁了。

缺点:synchronized 为独占排他锁,并发性能差。即使在创建成功以后,获取实例仍然是串行化操作。

package com.shenmazong;

/**
 * @program: server-java-demo
 * @description: 单例模式:懒汉式,线程安全
 * @author: 亮子说编程
 * @create: 2020-10-15 09:16
 **/
public class Singleton02 {
    private static Singleton02 instance;

    private Singleton02() {
    }

    // 加锁
    public static synchronized Singleton02 getInstance() {
        if (instance == null) {
            instance = new Singleton02();
        }
        return instance;
    }
}

这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,99%情况下不需要同步。

3)、饿汉式

优点:饿汉模式天生是线程安全的,使用时没有延迟。

缺点:启动时即创建实例,启动慢,有可能造成资源浪费。

package com.shenmazong;

/**
 * @program: server-java-demo
 * @description: 单例模式:饿汉一
 * @author: 亮子说编程
 * @create: 2020-10-15 09:24
 **/
public class Singleton03 {
    private static Singleton03 instance = new Singleton03();

    private Singleton03() {
    }

    public static Singleton03 getInstance() {
        return instance;
    }
}

这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。

4)、懒汉模式–双重加锁检查DCL(Double Check Lock)

优点:懒加载,线程安全。

注:实例必须有 volatile 关键字修饰,其保证初始化完全。

package com.shenmazong.core;

/**
 * @program: server-java-demo
 * @description: Singleton05
 * @author: 亮子说编程
 * @create: 2020-10-15 11:23
 **/
public class Singleton05 {

    // 对保存实例的变量添加volatile的修饰
    private volatile static Singleton05 instance = null;

    private Singleton05(){

    }

    public static Singleton05 getInstance(){

        //先检查实例是否存在,如果不存在才进入下面的同步块
        if(instance == null){

            //同步块,线程安全的创建实例
            synchronized(Singleton05.class){

                //再次检查实例是否存在,如果不存在才真的创建实例
                if(instance == null){
                    instance = new Singleton05();
                }
            }
        }

        return instance;
    }
}

5)、Holder模式(静态内部类)

优点:将懒加载和线程安全完美结合的一种方式(无锁)。(推荐)

package com.shenmazong.core;

/**
 * @program: server-java-demo
 * @description: 单例模式:Holder模式(静态内部类)
 * @author: 亮子说编程
 * @create: 2020-10-15 09:31
 **/

public class Singleton04 {

    /**
    * @Description: 
     * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
     * 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载
    * @Param: 
    * @return: 
    * @Author: 亮子说编程
    * @Date: 2020/10/15 11:19
    */
    private static class SingletonHolder{

         // 静态初始化器,由JVM来保证线程安全
        private static Singleton04 instance = new Singleton04();
    }

    /**
    * @Description: 私有化构造方法
    * @Param: []
    * @return: 
    * @Author: 亮子说编程
    * @Date: 2020/10/15 11:18
    */
    private Singleton04(){
    }

    public static  Singleton04 getInstance(){
        return SingletonHolder.instance;
    }
}

5、单例模式的问题

有两个问题需要注意:

1)、如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类 装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。

2)、如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。

1)、问题一的解决方案

    private static Class getClass(String classname)
            throws ClassNotFoundException {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

        if(classLoader == null) {
            classLoader = Singleton01.class.getClassLoader();
        }

        return (classLoader.loadClass(classname));
    }

2)、问题二的解决方案

public class Singleton06 implements java.io.Serializable {
    public static Singleton06 INSTANCE = new Singleton06();

    protected Singleton06() {

    }
    private Object readResolve() {
        return INSTANCE;
    }
}