头图

单例模式

  • 定义:保证一个类仅有一个实例,并提供一个全局访问点
  • 类型:创建型
  • 适用场景:想确保任何情况下都绝对只有一个实例
  • 优点:

    • 在内存里只有一个实例,减少了内存开销
    • 可以避免对资源的多重占用
    • 设置全局访问点,严格控制访问
  • 缺点:没有接口,扩展困难
  • 重点:

    • 私有构造器
    • 线程安全
    • 延迟加载
    • 序列化和反序列化安全
    • 反射
  • Double Check

image-20210718102245428.png

image-20210718102310010.png

  • 静态内部类:

    image-20210718102343496.png

  • 实用技能

    • 反编译
    • 内存原理
    • 多线程Debug

      image-20210718104520093.png

  • 相关设计模式

    • 单例模式和工厂模式
    • 单例模式和享元模式
代码示例
懒汉式
public class LazySingleton {
    private static LazySingleton lazySingleton;

    private LazySingleton() {
    }

    /**
     * synchronized(同步锁比较消耗资源)
     * 加在静态方法上:锁的是类的class文件
     * 加在普通方法上:锁的是堆内存中的对象
     * @return
     */
    public synchronized static LazySingleton getInstance(){//懒汉式
        if(lazySingleton==null){
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}
public class T implements Runnable{
    @Override
    public void run() {
        LazySingleton lazySingleton = LazySingleton.getInstance();
        System.out.println(Thread.currentThread()+" "+lazySingleton);
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
//        LazySingleton lazySingleton = LazySingleton.getInstance();
        Thread t1  = new Thread(new T());
        Thread t2  = new Thread(new T());
        t1.start();
        t2.start();
        System.out.println("program end");
/*        for (int i = 0; i < 10; i++) {
            new Thread(new T()).start();
        }*/
    }
}

双重校验(改进):

public class LazyDoubleCheckSingleton {
    // volatile多线程运行代码执行时重排序引起的问题(不允许重排序)
    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;
    }
}

基于类初始化的延迟加载解决方案:

/**
 * 基于类初始化的延迟加载解决方案
 */
public class StaticInnerClassSingleton {
    private StaticInnerClassSingleton() {
    }

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

    public static StaticInnerClassSingleton getInstance(){
        return InnerClass.staticInnerClassSingleton;
    }
}
public class T implements Runnable{
    @Override
    public void run() {
//        LazySingleton lazySingleton = LazySingleton.getInstance();
//        LazyDoubleCheckSingleton lazyDoubleCheckSingleton = LazyDoubleCheckSingleton.getInstance();
        StaticInnerClassSingleton staticInnerClassSingleton = StaticInnerClassSingleton.getInstance();
        System.out.println(Thread.currentThread()+" "+staticInnerClassSingleton);
    }
}
饿汉式
public class HungrySingleton {//饿汉式
    // 声明为final的变量必须在类加载时完成赋值
    private static final HungrySingleton hungrySingleton;
    static{
        hungrySingleton = new HungrySingleton();
    }

    private HungrySingleton() {
    }

    public static HungrySingleton getInstance() {
        return hungrySingleton;
    }
}
序列化破坏单例模式
public class HungrySingleton implements Serializable {//饿汉式
    // 声明为final的变量必须在类加载时完成赋值
    private static final HungrySingleton hungrySingleton;
    static{
        hungrySingleton = new HungrySingleton();
    }

    private HungrySingleton() {
    }

    public static HungrySingleton getInstance() {
        return hungrySingleton;
    }

    /**
     * 如果不写该方法,反序列化时会通过反射创建一个新的对象并返回,与序列化的对象不是同一对象。
     * 若写了该方法,则反序列化时,虽然通过反射创建一个新的对象,但会返回该对象。
     * @return
     */
    private Object readResolve(){
        return hungrySingleton;
    }
}
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);
    }
}
反射攻击解决方案
public class HungrySingleton implements Serializable {//饿汉式
    // 声明为final的变量必须在类加载时完成赋值
    private static final HungrySingleton hungrySingleton;
    static{
        hungrySingleton = new HungrySingleton();
    }

    private HungrySingleton() {
        if (hungrySingleton!=null){
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }

    public static HungrySingleton getInstance() {
        return hungrySingleton;
    }
}
public class StaticInnerClassSingleton {
    private StaticInnerClassSingleton() {
        if (InnerClass.staticInnerClassSingleton!=null){
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }

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

    public static StaticInnerClassSingleton getInstance(){
        return InnerClass.staticInnerClassSingleton;
    }
}
public class LazySingleton {
    private static LazySingleton lazySingleton;

    private LazySingleton() {
        if (lazySingleton!=null){
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }

    public synchronized static LazySingleton getInstance(){//懒汉式
        if(lazySingleton==null){
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

测试类:

/**
*可以解决饿汉式的反射攻击,但没有解决懒汉式的反射攻击。
*/
public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//        Class hungrySingletonClass = HungrySingleton.class;
//        Class objectClass = StaticInnerClassSingleton.class;

        Class objectClass = LazySingleton.class;

        Constructor constructor = objectClass.getDeclaredConstructor();
        constructor.setAccessible(true);
//        HungrySingleton instance = HungrySingleton.getInstance();
//        HungrySingleton newInstance  = (HungrySingleton) constructor.newInstance();

//        StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();
//        StaticInnerClassSingleton newInstance  = (StaticInnerClassSingleton) constructor.newInstance();

        LazySingleton newInstance = (LazySingleton) constructor.newInstance();
        LazySingleton instance = LazySingleton.getInstance();

        System.out.println(instance);
        System.out.println(newInstance);
        System.out.println(instance==newInstance);
    }
}
Enum枚举单例

Jad:反编译工具

序列化

public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        
//        HungrySingleton instance = HungrySingleton.getInstance();
        EnumInstance instance = EnumInstance.getInstance();
        instance.setData(new Object());
        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();
        EnumInstance newInstance = (EnumInstance) ois.readObject();

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

反射

public enum EnumInstance {
    INSTANCE{
        protected void printTest(){
            System.out.println("Feyl Print Test");
        }
    };
    protected abstract void printTest();
    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
    public static EnumInstance getInstance(){
        return INSTANCE;
    }
}
public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {


        Class objectClass = EnumInstance.class;
        Constructor constructor = objectClass.getDeclaredConstructor(String.class,int.class);
        constructor.setAccessible(true);
        EnumInstance instance = (EnumInstance) constructor.newInstance("Feyl",666);

        
//        EnumInstance instance = EnumInstance.getInstance();
//        instance.printTest();
    }
}
容器单例
public class ContainerSingleton {// 线程不安全
    private static Map<String,Object> singletonMap = new HashMap<>();

    private ContainerSingleton() {
    }

    public static void putInstance(String key, Object instance){
        if (!StringUtils.isNotBlank(key)&&instance!=null){
            if(!singletonMap.containsKey(key)){
                singletonMap.put(key,instance);
            }
        }
    }

    public static Object getInstance(String key){
        return singletonMap.get(key);
    }
}
public class T implements Runnable{
    @Override
    public void run() {
        ContainerSingleton.putInstance("object",new Object());
        Object instance = ContainerSingleton.getInstance("object");
        System.out.println(Thread.currentThread()+" "+instance);
    }
}

测试类:

public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Thread t1  = new Thread(new T());
        Thread t2  = new Thread(new T());
        t1.start();
        t2.start();
        System.out.println("program end");
    }
}
TreadLocal线程单例
public class ThreadLocalInstance {
    private static final ThreadLocal<ThreadLocalInstance> threadLocalInstance
            = new ThreadLocal<ThreadLocalInstance>(){
        @Override
        protected ThreadLocalInstance initialValue() {
            return new ThreadLocalInstance();
        }
    };

    private ThreadLocalInstance(){ }

    public static ThreadLocalInstance getInstance() {
        return threadLocalInstance.get();
    }
}
public class T implements Runnable{
    @Override
    public void run() {
        ThreadLocalInstance instance = ThreadLocalInstance.getInstance();
        System.out.println(Thread.currentThread()+" "+instance);
    }
}

测试类同上

“以空间换时间”:ThreadLocal 为每一个线程创建一个空间,各个线程获取其独有的对象。

Feyl
18 声望1 粉丝

没有梦想,何必远方?