设计模式——单例模式

 相关链接:

【设计模式】专栏:【设计模式】专栏

相关例子代码可下载: Java常用设计模式例子

单例模式

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

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

所谓单例,就是整个程序有且仅有一个实例。该类负责创建自己的对象,同时确保只有一个对象被创建。在Java,一般常用在工具类的实现或创建对象需要消耗资源。

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

        单例模式在现实生活中的应用也非常广泛,例如公司 CEO、部门经理等都属于单例模型。J2EE 标准中的 ServletContext 和 ServletContextConfig、Spring 框架应用中的 ApplicationContext、数据库中的连接池等也都是单例模式。

单例模式的特点

  • 单例类只能有一个实例。

  • 单例类必须自己创建自己的唯一实例。

  • 单例类必须给所有其他对象提供这一实例。

单例模式的优点和缺点

优点:

  • 单例模式可以保证内存内只有一个实例,减少了内存的开销

  • 可以避免对资源的多重占用

  • 单例模式设置全局访问点,可以优化和共享资源的访问

缺点:

  • 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则

  • 在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。

  • 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。

单例模式的8种方式

  1. 饿汉式(静态常量)

  2. 饿汉式(静态代码块)

  3. 懒汉式(线程不安全)

  4. 懒汉式(线程安全,同步方法)

  5. 懒汉式(线程安全,同步代码块)

  6. 双重检查

  7. 静态内部类

  8. 枚举

饿汉式(静态常量)

饿汉式(静态常量)应用实例,步骤如下:

  1. 构造器私有化(防止 new)

  2. 类的内部创建对象

  3. 向外暴露一个静态的公共方法。如:getInstance()

  4. 代码实现

优点

  • 类加载的时候就完成实例化,避免了线程同步问题

缺点

  • 没有达到懒加载lazy loading的效果,若从未使用过instance这个实例,则会造成内存的浪费

示例代码

单例模式:SingletonEHan01.java

public class SingletonEHan01 {

    /**
     * 1. 构造器私有化,外部不能 new
     */
    private SingletonEHan01() {}

    /**
     * 2. 本类内部创建对象实例
     */
    private final static SingletonEHan01 instance = new SingletonEHan01();

    /**
     * 3. 提供公有的静态方法,返回对象实例
     * @return SingletonEHan01
     */
    public static SingletonEHan01 getInstance() {
        return instance;
    }

    /**
     * 4. 代码实现
     */
    public void hello() {
        System.out.println("饿汉式(静态变量):Hello world!");
    }

}

调用方:SingletonPattern.java

public class SingletonPattern {

    public static void main(String[] args) {
        // 1. 饿汉式(静态变量)
        SingletonEHan01 singletonEHan01 = SingletonEHan01.getInstance();
        singletonEHan01.hello();
    }
}

饿汉式(静态代码块)

饿汉式(静态代码块)应用实例,步骤如下:

  1. 构造器私有化(防止 new)

  2. 本类内部定义一个静态的 实例变量,在静态代码块中创建实例对象

  3. 向外暴露一个静态的公共方法。如:getInstance()

  4. 代码实现

优点

  • 类加载的时候就完成实例化,避免了线程同步问题

缺点

  • 没有达到懒加载lazy loading的效果,若从未使用过instance这个实例,则会造成内存的浪费

示例代码

单例模式:SingletonEHan02.java

public class SingletonEHan02 {

    /** 1. 构造器私有化,外部不能 new */
    private SingletonEHan02() {}

    /** 2. 本类内部定义静态的实例变量 */
    private static SingletonEHan02 instance;

    static {
        // 静态代码块中创建实例对象
        instance = new SingletonEHan02();
    }

    /**
     * 3. 提供公有的静态方法,返回对象实例
     * @return SingletonEHan02
     */
    public static SingletonEHan02 getInstance() {
        return instance;
    }

    /**
     * 4. 代码实现
     */
    public void hello() {
        System.out.println("饿汉式(静态代码块):Hello world!");
    }

}

调用方:SingletonPattern.java

public class SingletonPattern {

    public static void main(String[] args) {
        // 2. 饿汉式(静态代码块)
        SingletonEHan02 singletonEHan02 = SingletonEHan02.getInstance();
        singletonEHan02.hello();
    }
}

懒汉式(线程不安全,实际开发中不可使用)

优缺点说明

  1. 起到了 Lazy Loading(懒加载) 的效果,但是只能在单线程下使用

  2. 如果在多线程下,一个线程进入了 if (singleton == null) 判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时候便会产生多个实例。所以在多线程环境下不可使用这种方式

结论:

在实际开发中,不要使用这种方式!

示例代码

单例模式:SingletonLanHan01.java

public class SingletonLanHan01 {

    /** 1. 构造器私有化,外部不能 new */
    private SingletonLanHan01() {}

    /** 2. 本类内部定义静态的实例变量 */
    private static SingletonLanHan01 instance;

    /** 3. 提供公有的静态方法,当使用到该方法时才去创建 instance */
    public static SingletonLanHan01 getInstance() {
        // 在多线程下,若多个线程都通过了该判断,便会产生多个实例,所以多线程环境下不推荐使用
        if (instance == null) {
            instance = new SingletonLanHan01();
        }
        return instance;
    }

    /** 4. 代码实现 */
    public void hello() {
        System.out.println("懒汉式(线程不安全):Hello world!");
    }

}

调用方:SingletonPattern.java

public class SingletonPattern {

    public static void main(String[] args) {
        // 3. 懒汉式(线程不安全,实际开发中不推荐使用)
        SingletonLanHan01 singletonLanHan01 = SingletonLanHan01.getInstance();
        singletonLanHan01.hello();
    }
}

懒汉式(线程安全,同步方法)

提供静态的公有方法,加入了同步处理代码,即 synchronized 关键字,解决了线程不安全的问题。

但该种方法效率太低,不建议使用。

示例代码

单例模式:SingletonLanHan02.java

public class SingletonLanHan02 {

    /** 1. 构造器私有化,外部不能 new */
    private SingletonLanHan02() {}

    /** 2. 本类内部定义静态的实例变量 */
    private static SingletonLanHan02 instance;

    /**
     * 3. 提供公有的静态方法,加入同步处理代码,解决线程不安全的问题,当使用到该方法时才去创建 instance
     *
     * 注意:正因为加入 synchronized 关键字,则可能导致效率太低,不建议使用
     *
     * */
    public static synchronized SingletonLanHan02 getInstance() {
        if (instance == null) {
            instance = new SingletonLanHan02();
        }
        return instance;
    }

    /** 4. 代码实现 */
    public void hello() {
        System.out.println("懒汉式(线程安全,同步方法):Hello world!");
    }

}

调用方:SingletonPattern.java

public class SingletonPattern {

    public static void main(String[] args) {
        // 4. 懒汉式(线程安全,同步方法)
        SingletonLanHan02 singletonLanHan02 = SingletonLanHan02.getInstance();
        singletonLanHan02.hello();
    }
}

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

优缺点说明

  • 这种方法,本意是想对前面 同步方法(即:synchronized方式的改进,因为同步方法效率太低,改为同步生产实例化的代码块。

  • 但是这种同步并不能起到线程同步的作用,如果在多线程下,一个线程进入了 if (singleton == null) 判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时候便会产生多个实例。所以在多线程环境下不可使用这种方式

结论:

在实际开发中,不能使用这种方式!

示例代码

单例模式:SingletonLanHan03.java

public class SingletonLanHan03 {

    /** 1. 构造器私有化,外部不能 new */
    private SingletonLanHan03() {}

    /** 2. 本类内部定义静态的实例变量 */
    private static SingletonLanHan03 instance;

    /** 3. 提供公有的静态方法 */
    public static SingletonLanHan03 getInstance() {
        if (instance == null) {
            /**
             * 加入同步处理代码块
             *
             * 值得注意:
             * 在多线程下,若多个线程都通过了 if (instance == null) 判断,因此便还是会产生多个实例,所以多线程环境下不推荐使用
             */
            synchronized(SingletonLanHan03.class) {
                instance = new SingletonLanHan03();
            }
        }
        return instance;
    }

    /** 4. 代码实现 */
    public void hello() {
        System.out.println("懒汉式(线程安全,同步代码块):Hello world!");
    }

}

调用方:SingletonPattern.java

public class SingletonPattern {

    public static void main(String[] args) {
        // 5. 懒汉式(线程安全,同步代码块)
        SingletonLanHan03 singletonLanHan03 = SingletonLanHan03.getInstance();
        singletonLanHan03.hello();
    }
}

双重检查(推荐使用)

优缺点说明

  1. Double-Check概念是多线程开发中常使用到的,如代码中所示,我们进行了两次 if (singleton == null) 检查,这样就可以保证线程安全了。

  2. 这样实例化代码只需要执行一次,后面再次访问时,判断 if (singleton == null),直接return实例化对象,也避免了反复进行方法同步。

  3. 线程安全,延迟加载,效率较高

结论:

在实际开发中,推荐使用这种双重检查的单例设计模式!

示例代码

单例模式:SingletonDoubleCheck.java

public class SingletonDoubleCheck {

    /** 1. 构造器私有化,外部不能 new */
    private SingletonDoubleCheck() {}

    /** 2. 本类内部使用volatile关键字定义静态的实例变量,volatile 保证可见性和禁止指令重排序 */
    private static volatile SingletonDoubleCheck instance;

    /** 3. 提供公有的静态方法 */
    public static synchronized SingletonDoubleCheck getInstance() {
        // 加入双重检查代码,解决线程不安全的问题,解决懒加载问题
        if (instance == null) {
            synchronized (SingletonDoubleCheck.class) {
                if (instance == null) {
                    instance = new SingletonDoubleCheck();
                }
            }
        }
        return instance;
    }

    /** 4. 代码实现 */
    public void hello() {
        System.out.println("双重检查:Hello world!");
    }
}

调用方:SingletonPattern.java

public class SingletonPattern {

    public static void main(String[] args) {
        // 6. 双重检查
        SingletonDoubleCheck singletonDoubleCheck = SingletonDoubleCheck.getInstance();
        singletonDoubleCheck.hello();
    }
}

静态内部类(推荐使用)

外部类进行类加载时,静态内部类不会进行类加载,保证了懒加载问题。

当调用 getInstance()方法时,才会实例化静态内部类,才会使用到静态内部类的静态变量,JVM在类加载时, 是线程安全的。

优缺点说明

  1. 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。

  2. 静态内部类方式,在外部类被装载时并不会立即实例化,而是在需要实例化时,调用 getInstance()方法,才会装载内部类,从而完成外部类的实例化。

  3. 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。

  4. 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高

结论:

推荐使用!

示例代码

单例模式:SingletonInnerClass.java

public class SingletonInnerClass {

    /** 1. 构造器私有化,外部不能 new */
    private SingletonInnerClass() {}

    /** 2. 本类内部使用volatile关键字定义静态的实例变量 */
    private static volatile SingletonInnerClass instance;

    /** 3. 提供公有的静态方法 */
    public static synchronized SingletonInnerClass getInstance() {
        // 直接返回静态内部类的静态变量
        return SingletonInstance.INSTANCE;
    }

    /** 4. 提供一个静态内部类 */
    private static class SingletonInstance {
        private static final  SingletonInnerClass INSTANCE = new SingletonInnerClass();
    }

    /** 5. 代码实现 */
    public void hello() {
        System.out.println("静态内部类:Hello world!");
    }
}

调用方:SingletonPattern.java

public class SingletonPattern {

    public static void main(String[] args) {
        // 7. 静态内部类
        SingletonInnerClass singletonInnerClass = SingletonInnerClass.getInstance();
        singletonInnerClass.hello();
    }
}

枚举(推荐使用)

使用枚举来实现单例模式,不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。

结论:

推荐使用!

示例代码

单例模式:SingletonEnum.java

public enum SingletonEnum {

    /** 1. 实例化对象 */
    INSTANCE;

    /** 2. 代码实现 */
    public void hello() {
        System.out.println("枚举:Hello world!");
    }

}

调用方:SingletonPattern.java

public class SingletonPattern {

    public static void main(String[] args) {
        // 8. 枚举
        SingletonEnum.INSTANCE.hello();
    }
}

JAVA中的单例模式

java.lang.Runtime

结束语

1、更多设计模式内容请看【设计模式】专栏

2、相关例子代码可下载: Java常用设计模式例子

PS:  【 Java常用设计模式例子 】 内已包含 【设计模式】专栏 里涉及的代码,如果之前已下载过的同学,就不需要重复下载啦~

以上内容如有不正确或需要补充的地方,还请多多请教,会及时更新改正~

欢迎评论~ 感谢点赞~

上一篇:StarRocks报错close index channel failed/too many tablet versions


下一篇:【ElasticSearch】给ElasticSearch数据库配置慢查询日志