简单纪要:单例模式的几种创建方式,看这一篇就够了

单例介绍:

单例模式(Singleton)也叫单态模式,是设计模式中最为简单的一种模式,甚至有些模式大师都不称其为模式,称其为一种实现技巧,因为设计模式讲究对象之间的关系的抽象,而单例模式只有自己一个对象,也因此有些设计大师并把把其称为设计模式之一。

应用:

1、多线程的线程池的设计一般是采用单例模式,这是由于线程池要方便对池中的线程进行控制。

2、Spring框架中对象的创建,默认也是单例的,这样可以简化资源,每次请求访问的都是同一个对象。

2、Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。

3、网站的计数器,一般也是采用单例模式实现,否则难以同步。

4、应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。

5、数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。

有了基本的了解以后,我们来看单例的第一种实现;

单例之饿汉模式:

代码实现:

/**
 * Created by Rnan on 2022/4/9.
 */
public class SingletonHungry{

//    直接进行对象的初始化
    private static final SingletonHungry singletonLazy = new SingletonHungry();

//    私有化构造器,不允许通过构造器创建
    private SingletonHungry(){
    }

//    开放唯一获取对象的接口
    public static SingletonHungry getInstance(){
        return singletonLazy;
    }
}

注意:

1、饿汉模式就是,不管以后使用与否,直接就是new一个对象,如果对应一直未被调用,对应的对象也已创建成功,可以保证百分百的单例,对象不会被实例化多次;

2、构造方法用private修饰,保证只有他自己可以实例化这个对象,别人不能操作。

单例之懒汉模式:

代码实现(同步方法):

/**
 * Created by Rnan on 2022/4/9.
 */
public class SingletonLazy{

    private static SingletonLazy singletonLazy = null;

    private SingletonLazy() {
    }

    public static synchronized SingletonLazy getInstance(){
        if(singletonLazy == null){
            singletonLazy = new SingletonLazy();
        }
        return singletonLazy;
    }
}

注意:

同步方法也可以百分百的保证单例,但是由于synchronized的排他性,会导致该方法性能过低。

单例之DCL:

代码实现(双端检索版本):

/**
 * Created by Rnan on 2022/4/9.
 */
public class SingletonLazy{

    private static SingletonLazy singletonLazy = null;

    private SingletonLazy() {
    }

    public static  SingletonLazy getInstance(){
        if(singletonLazy == null){
            synchronized (SingletonLazy.class){
                if(singletonLazy == null){
                    singletonLazy = new SingletonLazy();
                }
            }
        }
        return singletonLazy;
    }
}

注意:

1、双端检锁是一种比较好的创建单例的方法,在对象为null的时候,只让一个线程去创建对象,其他线程直接获取对象即可;

2、当两个线程同时发现对象为null,只让一个线程进入同步代码块,完成对象的初始化,随后的线程如果再次进入同步代码块,发现对象已经被初始化,就直接返回对象的实例,不再进行创建,这是未加载情况下并行的场景,如果对象加载完成,直接再第一个判断为null的地方,就直接获取初始化好的对象了,不会进入同步代码块,提高性能;

问题:

这种方式看似无懈可击,但是由于操作系统的指令重排,会导致空指针异常;

单例之volatile + DCL

代码实现(volatile + 双端检索版本):

/**
 * Created by Rnan on 2022/4/9.
 */
class SingletonLazy{

    private static volatile SingletonLazy singletonLazy = null;

    private SingletonLazy() {
    }

    public static  SingletonLazy getInstance(){
        if(singletonLazy == null){
            synchronized (SingletonLazy.class){
                if(singletonLazy == null){
                    singletonLazy = new SingletonLazy();
                }
            }
        }
        return singletonLazy;
    }
}

注意:

1、singletonLazy = new SingletonLazy(); 这行代码在指令级别对应的三步操作

第一步:memory = allocate(); //1.分配对象内存空间
第二步:instance(memory);//2.初始化对象
第三步:nstance = memory;//3.设置instance指向刚分配对象的内存地址,这个时候instance != null;

如果第三步先于第二步执行,就会出现空指针问题;

2、volatile 的特性之一就是,禁止指令重排,能够禁止操作系统对指令进行优化,在多线程下保证线程安全;

单例之静态内部类:

代码实现(静态内部类):

/**
 * Created by Rnan on 2022/4/9.
 */
public class Singleton {
    private static class SingletonInno{
        public static SingletonInno instance = new SingletonInno();
    }
    public static SingletonInno getInstant(){
        return SingletonInno.instance;
    }
}

注意:

在内部类直接进行实例化,使用饿汉式进行加载,但这也并不完全是饿汉式,因为静态类不会主动加载,只有在主类被调用的时候,才会被加载,保证程序运行时不会提前创建对象而浪费内存,只有主动调用时,才创建实例,保证懒加载;

单例之枚举:

代码实现(枚举):

/**
 * Created by Rnan on 2022/4/9.
 */
enum SingletonEnum{
    INSTANT;

    public static  SingletonEnum getInstance(){
        return INSTANT;
    }
}

注意:

1、枚举不允许被继承,所以线程安全只能被初始化一次;

2、但是使用枚举不能被懒加载,只要调用静态方法,直接实例化对象;

以上就是单例模式的所有创建方法,感兴趣的小伙伴可以自己敲敲,有问题可以评论沟通,see you again!

posted @ 2022-09-12 10:02  爱吃螃蟹的牧羊人  阅读(514)  评论(0编辑  收藏  举报