每天一种设计模式之单例模式(Java实现)

每天一种设计模式之单例模式(Java实现)

概述

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

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

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

实现单例模式的关键是私有化创建逻辑,在创建的时候判断是否有这个实例存在

示例

以麦当劳为例,麦当劳只有一位老板Boss,麦当劳创建后只能new一个老板实例,不能存在第二个老板
在用到老板的时候(比如签字盖章),我们首先要去看有没有老板,如果有就直接让老板签字盖章,没有再去“请”老板

源码实现

简单实现

首先创建一个Boss老板类

public class Boss {
    //创建Boss唯一的实例
    private static Boss boss = new Boss();
    //私有化构造方法,防止外部创建行为
    private Boss(){

    }
    //定义唯一获取实例的方法
    public static Boss getInstance(){
        return boss;
    }
    //定义一个测试方法sign老板签字
    public void sign(){
        System.out.println("老板签字!");
    }
}

然后创建一个SingletonPattern测试类

public class SingletonPattern {
    public static void main(String[] args) {
        //获取唯一可用对象
        Boss boss = Boss.getInstance();
        //老板签字
        boss.sign();
    }
}

得到结果

老板签字!

懒汉线程不安全

这种模式严格意义上不是单例模式,因为线程不安全。而单例模式要求是线程安全的。
单线程下没有问题,多线程下不能正常工作!

public class Singleton {
    //定义唯一实例的引用
    private static Singleton instance;
    //私有化构造方法,防止外部创建实例
    private Singleton(){

    }
    //判断引用是否指向实例,如果为空创建实例,如果存在则返回实例
    public static Singleton getInstance(){
        if(instance==null){
            instance = new Singleton();
        }
        return instance;
    }
}

懒汉线程安全

这个是真正意义上的单例,也是最常见的实现方式.
唯一的区别就是给获取实例的方法加了一把"锁",防止在刚启动的时候就有多个线程访问,造成创建多个实例的情况.

public class Singleton {
    //定义唯一实例的引用
    private static Singleton instance;
    //私有化构造方法,防止外部创建实例
    private Singleton(){

    }
    //判断引用是否指向实例,如果为空创建实例,如果存在则返回实例,同时锁住方法,防止重复创建
    public static synchronized Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

饿汉

饿汉与懒汉的区别在于:
饿汉是先创建实例,需要的时候直接返回创建好的实例;
懒汉是先不创建,当对象的getInstance方法被调用的时候再创建对象,可能会存在被同时调用的情况,所以需要加锁;
饿汉比较常用,缺点是容易产生垃圾对象;并且类加载的时候就创建对象,会造成内存的浪费.

public class Singleton {
    //直接创建唯一实例,赋值给引用
    private static Singleton instance = new Singleton();
    //私有化构造方法,防止外部创建实例
    private Singleton(){

    }
    //创建获取实例方法的时候记住一定要public(不然你给谁用)
    public static Singleton getInstance(){
        return instance;
    }
}

DCL 双重校验锁

getInstance的性能决定了单例的性能.
DCL能够在多线程下保持高性能.
不同于懒汉式,DCL不是锁住整个方法,而是通过volatile保证变量不被编译器优化,同时只锁住对象,安全且高效;
缺点是实现难度相对较高.

//双重检验锁,兼具性能和安全,需要jdk1.5以上支持
public class Singleton {
    //私有化引用,同时使用volatile来防止编译器优化,保证每次更新后所有线程都读取到最新值
    private  volatile static Singleton singleton;
    //私有化构造方法,防止被外部创建
    private Singleton(){

    }
    //获取实例的唯一方法,同时在创建的时候加一个对象锁
    public static Singleton getInstance(){
        /*说明下为什么有双重if
        * 第一重if判断没有对象先锁住整个对象,防止其他线程读取
        * 第二重if再次确认中间没有别的线程创建了对象
        * 然后创建对象并返回
        * */
        if(singleton==null){
            synchronized (Singleton.class){
                if(singleton==null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

静态内部类/登记式

这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用
推荐使用这种方法,实现难度一般,但是效果和双检锁相同.

//登记式/静态内部类
public class Singleton {
    //创建静态内部类,在类里面创建实例
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    //私有化构造方法,防止外部创建
    private Singleton (){}
    //直接返回内部类中的实例,这样不用加锁,并且静态区访问也快
    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

枚举

这是剑走偏锋的方式,但是同时也是Effective Java 作者 Josh Bloch 提倡的方式,它实现起来更加简单,还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化

public enum Singleton {
    //在这里定义实例
    INSTANCE;
    //下面可以写任意的方法
    public void whateverMethod() {
        
    }
}

总结

单例模式可以说是面试里问的最多的了,以上的方法不一定就是所有的方法,我们更关键的就是要知道单例的核心"怎么让应用中只存在一个实例",别的都是和多线程,性能相关的问题,这个可以后期不断研究和优化.

发布了37 篇原创文章 · 获赞 35 · 访问量 6506

猜你喜欢

转载自blog.csdn.net/itkfdektxa/article/details/102952166