【设计模式】详解单例模式

什么是单例模式

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

注意事项

  • 单例模式只能有一个实例(只能有一个对象)
  • 单例类必须自己创建自己的唯一实例(在方法内部创建自己的对象)
  • 单例类必须给其他所有对象提供这一实例

要点

  • 私有构造方法
    将该类的构造函数私有化,目的是禁止其他程序创建该类的对象,同时也是为了提醒查看代码的人我这里是在使用单例模式,防止他人将这里任意修改。
  • 指向自己实例的私有静态引用
  • 以自己实例为返回值的静态共有方法
    为什么方法和引用都是静态
    程序调用类中方法只有两种方法
    1. 创建一个类的对象,用该对象去调用类中的方法
    2. 通过类名直接调用类中的方法
      因为单例模式构造函数私有化,所以不能通过第一种方法调用方法,只能通过第二种方式
      而使用类名调用的方法只能是静态方法,静态方法不能访问非静态成员变量,因此类自定义的实例变量也必须是静态的

有什么用?

应用举例:

  • 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
  • 一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。

单例模式实现

  1. 创建单例类
class SingleObject{
    private static SingleObject instance = new SingleObject();		//指向自己实例的私有静态引用
    private SingleObject(){}			//私有构造方法

    public static SingleObject getInstance() {		//以自己实例为返回值的静态共有方法
        return instance;
    }
}
  1. 获取单例类的唯一方法
public class Main {
    public static void main(String[] args) {
        SingleObject instance = SingleObject.getInstance();		//通过静态方法获取唯一的实例
    }
}

上面的单例是基于饿汉式,什么是饿汉式,继续往下看

单例模式的几种实现方式

1.饿汉式

**是否线程安全:**是
实现方式

class SingleObject{
    private static SingleObject instance = new SingleObject();		//定义时直接实例化
    private SingleObject(){}			//私有构造方法

    public static SingleObject getInstance() {		//以自己实例为返回值的静态共有方法
        return instance;
    }
}

基于classloader机制避免了多线程的同步问题
**优点:**没有加锁也可以实现线程安全,在安全的情况下效率变高
**缺点:**类加载的时候就初始化,浪费内存

2.懒汉式 线程不安全

**是否线程安全:**否
实现方式

class SingleObject{
    private static SingleObject instance;		//定义时不实例化
    private SingleObject(){}

    public static SingleObject getInstance() {
        if (instance==null) {			//如果已经被实例,直接返回
            instance = new SingleObject();		//没有被实例,创建实例后返回
        }
        return instance;
    }
}

严格意义上不属于单例模式
线程不安全,多线程不能工作

3.懒汉式 线程安全

**是否线程安全:**是
实现方式

class SingleObject{
    private static SingleObject instance;
    private SingleObject(){}

    public static synchronized SingleObject getInstance() {		//直接在instance上加synchronized锁
        if (instance==null) {
            instance = new SingleObject();
        }
        return instance;
    }
}

**优点:**第一次调用才初始化,对内存友好
**缺点:**synchronize锁效率很低

4.双检锁(DCL懒汉式)

JDK版本 1.5以后
是否线程安全:

class SingleObject{
    private static SingleObject instance;
    private SingleObject(){}

    public static SingleObject getInstance() {
        if (instance==null) {			//先判断是否为空
            synchronized (SingleObject.class) {	//如果为空就加锁
                if (instance == null) {			//可能在判断为空和加锁之间的时间又线程创建了这个实例,所以要再进行一步判断
                    instance = new SingleObject();
                }
            }
        }
        return instance;
    }
}

在线程安全懒汉式上减小了粒度,提高了性能
但是在极端情况下还是会有问题,因为instance = new SingleObject();不是原子操作,具体会分为三步操作

  1. 分配内存空间
  2. 执行构造方法
  3. 把对象指向这个空间
    可能会发生指令重排,导致指令按照132这个顺序执行,在极端情况下,线程A执行了13两个方法,这时有一个线程B进入了最外层的判断,这时因为instance已经有了内存空间,但是instance并没有被构造,所以B会返回一个空的内存空间
    所以要在instance前加volatile避免指令重排
class SingleObject{
    private static volatile SingleObject instance;			//避免指令重排
    private SingleObject(){}

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

但是这种情况还是有问题,如果我们用获得的唯一一个对象实例,使用反射来获取这个类的构造题,通过setAccessible(true)来修改构造器的权限,然后再通过构造器新建一个对象,就会导致创建出两个对象

public class Main {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        SingleObject instance = SingleObject.getInstance();
        Constructor<SingleObject> declaredConstructor = SingleObject.class.getDeclaredConstructor();        //获取构造器反射
        declaredConstructor.setAccessible(true);        //破坏权限
        SingleObject singleObject = declaredConstructor.newInstance();      //通过反射创建对象
    }
}

如果通过加锁和flag判断,也会出现反编译得到flag的变量名进行破译的操作
我们进入newInstance源码
在这里插入图片描述
可以看到枚举类型是不能用反射新建的
所以这里我们推荐用enum来创建单例模式

enum

JDK版本: JDK1.5起
线程安全:

enum SingleObject{
    INSTANCE;
    public static SingleObject getInstance() {
        return INSTANCE;
    }
}

这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
不能通过反射来进行实例化
参考文章
单例模式
【狂神说Java】单例模式-23种设计模式系列
单例模式
什么是单例模式?单例模式有什么作用?为什么要用单例模式
单例模式
单例模式中为什么构造函数为私有?为什么指向本身实例的类属性为静态?为什么以自己实例为返回值的类方法为静态?
为什么单例模式中的成员函数都是静态的?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值