Java单例模式

/ 2评 / 2

单例模式是保证一个类仅有一个实例,并提供一个全局访问点。属于创建型,也属于GOF23种设计模式。

适用范围

想确保任何情况下都绝对只有一个实例。

优缺点

优点:在内存里只有一个实例,减少了内存开销;可以避免对资源的多重占用;设置全局访问点,严格控制访问。

缺点:没有接口,扩展困难。

示例

懒汉式解决方案

我们可以使用 synchronized 锁来实现线程安全。

方式一:但是在 static 方法中使用 synchronized 锁是针对整个类的,是一个类锁(全局锁),锁的范围比较大,导致加锁解锁资源开销也比较大。下面示例中 getInstance1getInstance2 等同。

package com.zaiae.design.pattern.creational.singleton;

public class LazySingleton {
    private static LazySingleton lazySingleton;

    private LazySingleton() {
    }

    public static LazySingleton getInstance1() {
        synchronized (LazySingleton.class) {
            if (lazySingleton == null) {
                lazySingleton = new LazySingleton();
            }
        }
        return lazySingleton;
    }

    public synchronized static LazySingleton getInstance2() {
        if (lazySingleton == null) {
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

方式二:使用 volatile 关键字来“禁止指令重排序”,并采用双重检查方式,减少 synchronized 加锁解锁的次数,从而解决效率和线程安全问题。

package com.zaiae.design.pattern.creational.singleton;

public class LazyDoubleCheckSingleton {

    private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton;

    private LazyDoubleCheckSingleton() {

    }

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

注意:在上面示例中,创建对象并不是直接完成的。事实上会先分配对象的内存空间,然后初始化对象并让 lazyDoubleCheckSingleton 指向内存空间,最后才被线程访问。其中初始化对象和指向对象的内存空间并不是一定按顺序执行的,这样在多线程环境下就有可能发生对象还未初始化,就被其他线程访问了,从而导致异常发生。

所以我们可以使用 volatile 关键字来保证创建对象过程中不会出现重排序,从而避免这个问题。

方式三:基于类初始化的延迟加载,我们可以使用静态内部类的方式实现。在多线程环境中,当一个线程访问这个静态内部类,会添加一个 Class 对象的初始化锁( InnerClass 对象的初始化锁),让这个静态内部类初始化时,对象实例化过程中的重排序对其他线程不可见。

package com.zaiae.design.pattern.creational.singleton;

public class StaticInnerClassSingleton {
    private static class InnerClass {
        private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
    }

    public static StaticInnerClassSingleton getInstance() {
        return InnerClass.staticInnerClassSingleton;
    }

    private StaticInnerClassSingleton() {
        
    }
}

饿汉式解决方案

方式一:在类加载的时候就直接创建单例对象。

package com.zaiae.design.pattern.creational.singleton;

public class HungrySingleton {
    private static final HungrySingleton INSTANCE = new HungrySingleton();

    private HungrySingleton() {

    }

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

方式二:通过静态代码块创建单例对象。

package com.zaiae.design.pattern.creational.singleton;

public class HungrySingleton {
    private static final HungrySingleton INSTANCE;

    static {
        INSTANCE = new HungrySingleton();
    }
    
    private HungrySingleton() {

    }

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

序列化和反序列化

我们自己创建的类,实现 Serializable 接口,就能够实现序列化和反序列化。但是会导致单例对象被破坏,造成对象不是同一个,我们可以通过实现 readResolve() 方法解决这个问题。

package com.zaiae.design.pattern.creational.singleton;

import java.io.Serializable;

public class HungrySingleton implements Serializable {
    private static final HungrySingleton INSTANCE;

    static {
        INSTANCE = new HungrySingleton();
    }

    private HungrySingleton() {

    }

    public static HungrySingleton getInstance() {
        return INSTANCE;
    }

    private Object readResolve() {
        return INSTANCE;
    }
}

Test.java 测试类:

package com.zaiae.design.pattern.creational.singleton;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException {

        HungrySingleton instance = HungrySingleton.getInstance();
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
        oos.writeObject(instance);

        File file = new File("singleton_file");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        HungrySingleton newInstance = (HungrySingleton) ois.readObject();

        System.out.println(instance);
        System.out.println(newInstance);
        System.out.println(instance == newInstance);
    }
}

运行结果:

com.zaiae.design.pattern.creational.singleton.HungrySingleton@266474c2
com.zaiae.design.pattern.creational.singleton.HungrySingleton@266474c2
true

反射攻击

  1. 婚书网说道:

    已加入收藏夹,时不时的来看看有没有更新博文!

  2. 混球网说道:

    天气越来越冷了,躲在家里刷刷博客也挺好!

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注