单例设计模式(java与node实现)

本文详细介绍了单例设计模式的概念、作用及在Java中的多种实现方式,包括懒汉模式、饿汉模式、静态内部类实现以及线程安全的考虑。同时,讨论了反射、序列化对单例的影响及解决方案,并给出了Java.lang.Runtime作为单例模式在JDK中的应用实例。
摘要由CSDN通过智能技术生成

单例设计模式

什么是单例设计模式

单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例

具体实现

(1)将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。

(2)在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型。

(3)定义一个静态方法返回这个唯一对象。

java语言实现
懒汉模式

延迟加载,当只有使用的时候才开始真正的实例化

/**
 * 单例设计模式懒汉式
 */
public class SingleTonLazy {
    //定义一个实例化对象
    private static SingleTonLazy singleTonLazy = null;

    //构造方法私有化
    private SingleTonLazy(){}
    //静态工厂方法
    private static SingleTonLazy getInstance(){
        if (singleTonLazy == null){
            singleTonLazy = new SingleTonLazy();
        }
        return singleTonLazy;
    }
}

单例设计模式本身是线程安全的,因为从程序被创建就提供了一个静态类,除非关闭程序

但是在多线程模式下就会出现线程安全问题

加锁单例
public class SingleTonSync {
    private static SingleTonSync singleTonSync = null;
    //构造方法私有
    private SingleTonSync(){}

    public static SingleTonSync getSingleTonSync(){
        if (singleTonSync == null){
            //加锁操作
            synchronized (SingleTonSync.class){
                if (singleTonSync == null){
                    singleTonSync = new SingleTonSync();
                }
            }
        }

        return singleTonSync;
    }

}

当singleTonSync对象是空时进行加锁操作,来保证线程的安全

在编译器,cpu进行编译时有可能对指令进行重排序,导致尚未初始化的示例

什么意思呢?

创建对象的正常顺序:1分配空间,2初始化,3引用赋值

被重排序以后:1分配空间,3引用赋值,2初始化

假入有两个线程 T1,T2

T1首次创建对象被重排序以后,T2有可能在对象引用赋值之后,初始化之前访问,此时singleTonSync不为空

T2线程就直接返回singleTonSync对象,但是由于没初始化就会发生空指针等异常

那么如何来处理呢?

可以通过volatile关键字修饰,对于volatile修饰的字段,可以防止指令重排序

防止重排序
//加入volatile 对于volatile修饰的字段,可以防止指令重排序
private volatile static SingleTonSync singleTonSync = null;
//构造方法私有
private SingleTonSync(){}

public static SingleTonSync getSingleTonSync(){
    if (singleTonSync == null){
        //加锁操作
        synchronized (SingleTonSync.class){
            if (singleTonSync == null){
                singleTonSync = new SingleTonSync();
            }
        }
    }

    return singleTonSync;
}
饿汉模式
class SingleTon{
    private static SingleTon singleTon = new SingleTon();

    private SingleTon(){}

    public static SingleTon getSingleTon(){
        return singleTon;
    }
}

通过jvm的类加载机制来保证单例

静态内部类
class SingleTonStaticTest{
    //静态内部类
    private static class SingleTon{
        private static SingleTonStaticTest singleTonStaticTest = new SingleTonStaticTest();
    }
    private SingleTonStaticTest(){}

    public static SingleTonStaticTest getInstance(){
        return SingleTon.singleTonStaticTest;
    }
}

本质上是利用类的加载机制来保证线程的安全

只有在实际使用时才会触发类的初始化,所以也是懒加载的一种

反射创建单例对象的问题

通过反射来创建类会破坏单例

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
  //通过反射来创建单例对象
    Constructor<SingleTonStaticTest> sin = SingleTonStaticTest.class.getDeclaredConstructor();
    sin.setAccessible(true);
    SingleTonStaticTest singleTonStaticTest = sin.newInstance();
    SingleTonStaticTest instance = SingleTonStaticTest.getInstance();
    System.out.println(singleTonStaticTest == instance);
}

这里的两个对象就不是单例对象

那么怎么来解决呢?

懒汉模式是不能解决的,懒汉模式的单例对象应当避免使用反射的方式创建

饿汉模式和静态内部类可以通过异常处理解决

class SingleTonStaticTest{
    //静态内部类
    private static class SingleTon{
        private static SingleTonStaticTest singleTonStaticTest = new SingleTonStaticTest();
    }
    private SingleTonStaticTest(){
        //解决反射创建单例对象的问题
        if (SingleTon.singleTonStaticTest!=null){
            throw new RuntimeException("单例模式不允许创建多个对象");
        }
    }
    public static SingleTonStaticTest getInstance(){
        return SingleTon.singleTonStaticTest;
    }
}

那么是不是所有的类都能通过反射创建呢?

查看newInstance的源码发现

if ((clazz.getModifiers() & Modifier.ENUM) != 0)
    throw new IllegalArgumentException("Cannot reflectively create enum objects");

当类型为枚举时是会抛出一个异常的

枚举类型
/**
 * 枚举类型单例
 */
public enum SingleTonEnmu {
    INSTANCE;
}
反序列化创建对象的问题

话不多说看代码

public class SingleTonStatic {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException {
      	//获取到单例对象
        SingleTonStaticTest instance = SingleTonStaticTest.getInstance();
     		//序列化对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"));
        oos.writeObject(instance);
        oos.close();
      	//反序列化对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test"));
        SingleTonStaticTest singleTonStaticTest = ((SingleTonStaticTest) ois.readObject());
        
      //输出false
      System.out.println(instance == singleTonStaticTest);

    }
}
class SingleTonStaticTest implements Serializable {
    //静态内部类
    private static class SingleTon{
        private static SingleTonStaticTest singleTonStaticTest = new SingleTonStaticTest();
    }
    private SingleTonStaticTest(){
        //解决反射创建单例对象的问题
        if (SingleTon.singleTonStaticTest!=null){
            throw new RuntimeException("单例模式不允许创建多个对象");
        }
    }
    public static SingleTonStaticTest getInstance(){
        return SingleTon.singleTonStaticTest;
    }
}

反序列化创建对象字节流中获取数据不会通过类的构造函数

那么如何解决?

查看Serializable接口的源码

* Classes that need to designate a replacement when an instance of it
* is read from the stream should implement this special method with the
* exact signature.
*
* <PRE>
* ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
* </PRE><p>

提供一个readResolve方法

改造反序列化创建对象
public class SingleTonStatic {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException {
				//获取到单例对象
        SingleTonStaticTest instance = SingleTonStaticTest.getInstance();
     		//序列化对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"));
        oos.writeObject(instance);
        oos.close();
      	//反序列化对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test"));
        SingleTonStaticTest singleTonStaticTest = ((SingleTonStaticTest) ois.readObject());
        
      //输出true
      System.out.println(instance == singleTonStaticTest);

    }
    }
}
class SingleTonStaticTest implements Serializable {
  	//序列化版本号
    static final long serialVersionUID = 42L;
    //静态内部类
    private static class SingleTon{
        private static SingleTonStaticTest singleTonStaticTest = new SingleTonStaticTest();
    }
    private SingleTonStaticTest(){
        //解决反射创建单例对象的问题
        if (SingleTon.singleTonStaticTest!=null){
            throw new RuntimeException("单例模式不允许创建多个对象");
        }
    }
    public static SingleTonStaticTest getInstance(){
        return SingleTon.singleTonStaticTest;
    }
  //返回自己的类
    Object readResolve() throws ObjectStreamException{
        return SingleTon.singleTonStaticTest;
    }
}
反序列化枚举类型

刚刚提到了单例对象可以通过反序列化被破坏,那么枚举类型会怎样呢?

查看反序列枚举类型的实现

查看readObject方法源码

//拿到对应的枚举类型
String name = readString(false);
Enum<?> result = null;
Class<?> cl = desc.forClass();
if (cl != null) {
    try {
        @SuppressWarnings("unchecked")
      //获取对应的实例
        Enum<?> en = Enum.valueOf((Class)cl, name);
        result = en;
    } catch (IllegalArgumentException ex) {
        throw (IOException) new InvalidObjectException(
            "enum constant " + name + " does not exist in " +
            cl).initCause(ex);
    }
    if (!unshared) {
        handles.setObject(enumHandle, result);
    }
}

通过源码我们可以看到valueof方法是一个静态方法

public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                            String name) {
    T result = enumType.enumConstantDirectory().get(name);
    if (result != null)
        return result;
    if (name == null)
        throw new NullPointerException("Name is null");
    throw new IllegalArgumentException(
        "No enum constant " + enumType.getCanonicalName() + "." + name);
}

综上枚举类型的单例对象对反序列有着天然的优势,换句话讲 通过反序列化的方式不会破坏枚举类型的单例对象

单例对象在jdk中的应用

java.lang.Runtime

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class <code>Runtime</code> are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return  the <code>Runtime</code> object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime() {}
    }

饿汉模式在jdk中的实现

node实现
单例核心代码
// 构造函数

function Person(){
    this.name = '张三'
}
// 单例核心代码
let instance = null
function singleTon(){
    if(!instance) instance = new Person()

    return instance
}

使用闭包对单例模式进行改造
//使用闭包对单例核心代码进行改造

const singleTon = ( function(){
    // 构造函数
    function Person(){
        this.name = '张三'
    }
    let instance = null
    return function singleTon(){
        if(!instance) instance = new Person()

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值