美文网首页
设计模式之单例模式

设计模式之单例模式

作者: 蜗牛是不是牛 | 来源:发表于2023-05-28 17:09 被阅读0次

单例模式是一种非常常见的设计模式,其定义为:程序运行时,在java虚拟机中只存在该类的一个实例对象。

接下来着重介绍单例模式的几种实现方式:

1、饿汉式

public class Singleton {
    // 定义变量时直接初始化
    private static Singleton instance = new Singleton();
    // 构造方法私有,不允许外部new
    private Singleton() {
    }
    // 外部通过getIntstance获取实例
    public static Singleton getInstance() {
        return instance;
    }
}

饿汉式实现方式的关键在于定义instance时会直接进行实例化,因此通过饿汉式完全可以保证实例对象的线程安全。

但是饿汉式存在的一个问题是:如果该实例对象在被实例化很久之后才会被访问,那么在访问之前这个对象数据会一直存放在堆内存当中,如果实际场景中单例对象的实例数据很大,将会占用比较多的资源,此时这种方式则不太合适。

2、懒汉式

public class Singleton {
    // 定义变量时不做初始化
    private static Singleton instance = null;
    // 构造方法私有,不允许外部new
    private Singleton() {
    }
    // 外部通过getIntstance获取实例
    public static Singleton getInstance() {
        if(instance==null) {
            instance = new Singleton();
        }
        return instance;
    }
}

懒汉式相比于饿汉式,区别的地方主要在创建对象的时机有区别,不会在定义变量时初始化,而是在需要使用时才进行创建。因此可以避免在使用对象之前创建对象造成的空间资源浪费。

其存在的问题为:在多线程调用时,会存在线程安全问题,因为getInstance()方法中的if判断和对instance的赋值动作不是原子操作。例如:线程A和线程B先后调用getInstance(),线程A先判断instance==null为true,在线程A进行实例化对象之前,线程B拿到了CPU的执行权,这时线程B判断instance==null也为true,此时线程B也会做实例化,这样无法保证实例的唯一性。

3、线程安全的懒汉式

public class Singleton {
    // 定义变量时不做初始化
    private static Singleton instance = null;
    // 构造方法私有,不允许外部new
    private Singleton() {
    }
    // 方法上加synchronized保证线程安全
    public static synchronized Singleton getInstance() {
        if(instance==null) {
            instance = new Singleton();
        }
        return instance;
    }
}

通过synchronized关键字对getInstance()方法上锁,从而保证多线程情况下只能有一个线程进入此方法,也就保证了线程安全。但该方法在实例对象创建成功后,每次获取实例对象时依旧会加锁,因此性能损失是很高的。

4、双重检查锁

public class Singleton {
    private static volatile Singleton instance = null;
    private Singleton() {
    }
    public static Singleton getInstance() {
        // 第一层检查
        if (instance == null) {
            // 只能有一个线程获得Singleton.class锁
            synchronized (Singleton.class) {
                // 第二层检查
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

第一层instance==null的判断如果为true,则会执行加锁和创建实例的操作;如果第一层判断为false,表示对象已经创建完成了,那么直接返回实例就OK,避免每次请求都加锁,性能高。 在第一层判断结果为true,则需要加锁保证实例创建过程的安全性。 加锁成功后的第二层判空检查的目的是为了防止在进入第一层检查和加锁成功的过程中已经有其他线程完成实例的创建,避免重复创建

并且,通过volatile修饰instance,可以保证对instance实例化的操作不会被指令重排序,

5、静态内部类

public class Singleton {
    private Singleton() {
    }
    public static Singleton getInstance() {
        // 实际是返回静态内部类中的实例
        return Holder.instance;
    }

    private static class Holder {
        // 在静态内部类中定义instance并实例化
        private static Singleton instance = new Singleton();
    }
}

该方法是通过类加载机制来实现的,静态内部类并不会随着外部类的加载一起加载,只有在使用时才会加载;而类加载的过程则直接保证了线程安全性,保证实例对象的唯一。

这种方式又被称为IoDH(Initialization Demand Holder)技术,是目前使用比较广的方式之一,也算是最好的一种单例设计模式了。

6、枚举

public enum Singleton {
    INSTANCE;

    Singleton() {
    }
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

通过枚举实现单例模式的方法是线程安全的,并且只会被实例化一次。该方法主要是为了防止通过反射来破坏单例,使用反射来创建类的实例,即使类的构造方法私有时,也可以创建,这样就可能导致单例模式被破坏。

相关文章

网友评论

      本文标题:设计模式之单例模式

      本文链接:https://www.haomeiwen.com/subject/wfydedtx.html