一文带你了解 Java 五种单例模式的实现方式以及应用场景

单例模式

什么是单例模式

类的单例设计模式,就是采取一定的方法保证在整个软件系统中,某个类只能存在一个对象实例,并且这个类会提供一个获取对象实例的方法。

思路:如果让一个类在一个虚拟机里面只能产生一个对象,就必须将构造器的访问权限设置为private,这样在外部,就不能new这个对象了,只能在内部实例化这个对象。因为外部 不能获取,所以可以通过调用一个静态方法,返回类的内部创建的静态对象。

两大种实现方式

饿汉式

饿汉式实现举例

package com.designPattren;

/**
 * 饿汉式单例模式
 */
public class SingleHungry {
    private static final SingleHungry SINGLE_HUNGRY_INSTANCE = new SingleHungry();

    /**
     * 防止反射破坏单例
     */
    private SingleHungry() {
        if (SINGLE_HUNGRY_INSTANCE != null) {
            throw new RuntimeException("不能重复创建单例");
        }
        System.out.println("singleton");
    }

    public static SingleHungry getInstance() {
        return SINGLE_HUNGRY_INSTANCE;
    }

    /**
     * 重写readResolve方法,防止反序列化破坏单例
     * @return
     */
    public Object readResolve() {
        return SINGLE_HUNGRY_INSTANCE;
    }

    /**
     * 测试方法,懒汉还是饿汉式
     */
    public static void otherMethod() {
        System.out.println("aaaaaa");
    }

}

注意:如果构造方法不对单例对象是否实现进行判断,单例就可能被反射破坏;
readResolve方法的重写是防止反序列化破坏单例,重写这个方法之后反序列化就不返回字节数组了,返回这个单例对象。
使用这种方式创建单例对象,是不能方式Unsafe破坏单例的

枚举式饿汉:


/**
 * 枚举类创建单例
 */
public enum SingletonHungryBuEnum {
    INSTANCE;

    SingletonHungryBuEnum() {
        System.out.println("singleton");
    }

    public SingletonHungryBuEnum getInstance() {
        return INSTANCE;
    }

    public void otherMethod() {
        System.out.println("aaaaa");
    }

}

枚举饿汉式能天然防止反射、反序列化破坏单例

懒汉式

懒汉式不同于饿汉式,懒汉式单例只有调用这个单例对象的时候才创建对象。

public class Singleton3 implements Serializable {
    private Singleton3() {
        System.out.println("private Singleton3()");
    }

    private static Singleton3 INSTANCE = null;

    // Singleton3.class
    public static synchronized Singleton3 getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton3();
       
        return INSTANCE;
    }
//这里为了测试类初始化的时候,是否调用构造方法,如果调用了构造方法,也就意味着对象被初始化了
    public static void otherMethod() {
        System.out.println("otherMethod()");
    }

}

这样锁粒度太大了。实际上需要同步的时候只是在首次创建单例对象的时候。所以可以改进一下

双检锁懒汉式

public class Singleton4 implements Serializable {
    private Singleton4() {
        System.out.println("private Singleton4()");
    }

    private static volatile Singleton4 INSTANCE = null; // 可见性,有序性

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

    public static void otherMethod() {
        System.out.println("otherMethod()");
    }
}

前文我们加锁是在整个getInstance方法上加锁,但是粒度太大了。我们只需要在创建新对象的时候加锁就可以了。但是如果只有第一层
INSTANCE == null判断,那么可能会出现下面的问题:A线程占有资源B进程挂起,但是if判断B线程是可以进去的,A释放资源之后B又创建新对象了,为了避免这种问题,我们在同步代码快里面再加一个判断。双检机制解决了重复创建对象的问题。
但是这并不是完全安全的,在整个对象创建的过程,其实是分三步的,创建对象,调用构造方法,给静态变量赋值。创建对象毋庸置疑是第一步执行的,但是后面两步,它实际上先后执行的顺序是不确定的。JVM可能 先putstatic,在执行构造方法init对象。如果先putstatic,那么当前单例对象就不是空了,其他线程在这个时候如果来获取单例对象,就可能获取到未被构造方法初始化的对象。为了避免这种问题,在单例对象上加volatile关键字。保证单例对象的有序性。加关键字之后,执行构造方法这一步骤就会加上内存屏障,putstatic只能在构造方法init对象之后才能执行。避免了指令重排序。解决了上述的问题。

内部类懒汉式

内部类相较于上述实现方法,实现比较简单,利用静态内部类的特性。只有在首次使用静态内部类的时候静态内部类才会加载。这样我们在实现单例的时候只要把单例对象定义在内部类里,当调用getInstance的时候,静态内部类的静态变量就实例化。静态变量的初始化是在静态代码块里面的,天然的避免了线程安全。

public class Test {
    public static void main(String[] args) {
        otherMethod();
        System.out.println("-----------------------");
        /*Test instance = new Test().getInstance();
        System.out.println(instance == null ? "1" : "0");*/
    }
    Test() {
        System.out.println("go To init");
    }

    static class a{
        private static final Test INSTANCE = new Test();
    }   
    public static Test getInstance() {
        return a.INSTANCE;
    }

    /**
     * 测试是否调用构造方法,看这个变量有没有先创建。
     */
    public static void otherMethod() {
        System.out.println("om");
    }

}

在JDK中的体现。

java.lang.RunTime 典型的饿汉式单例创建
在这里插入图片描述
java.io.Console 双捡锁懒汉式单例模式
在这里插入图片描述

  • Collections 中的 EmptyNavigableSet 内部类懒汉式单例
    在这里插入图片描述

  • ReverseComparator.REVERSE_ORDER 内部类懒汉式单例

  • Comparators.NaturalOrderComparator.INSTANCE 枚举饿汉式单例

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值