Java设计模式——单例模式

设计模式是一套被反复使用、多人知晓、经过分类编目、代码设计经验的总结。是人们实践的产物。

单例模式能够保证整个应用中有且只有一个实例,即保证一个类在内存中的对象的唯一性。单例模式使用一个私有构造函数、一个私有静态变量、一个公有静态函数来实现。

五种实现方式

饿汉式

import java.io.Serializable;

/**
 * @author Margaret
 * @create 2022-01-13 16:02
 */
//饿汉式
public class Singleton1 implements Serializable{
    private Singleton1() {
        System.out.println("这是私有构造函数");
    }

    public static Singleton1 instance = new Singleton1();

    public static Singleton1 getInstance() {
        return instance;
    }

    public static void otherMethod() {
        System.out.println("其他方法");
    }
}

写一个测试方法:

public class SingletonTest {
    public static void main(String[] args) {
        Singleton1.otherMethod();
        System.out.println("========================================");
        System.out.println(Singleton0.getInstance());
        System.out.println(Singleton0.getInstance());
    }
}

输出结果:
在这里插入图片描述
但是,可以通过反射和反序列化以及unsafe()来破坏单例,如:

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * @author Margaret
 * @create 2022-01-13 16:03
 */
public class SingletonTest {
    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, IOException, ClassNotFoundException {
        Singleton1.otherMethod();
        System.out.println("==========================");
        System.out.println(Singleton1.getInstance());
        System.out.println(Singleton1.getInstance());

        //反射破坏单例
        reflection(Singleton1.class);

        //反序列破坏单例
        //serializable(Singleton1.getInstance());
        //Unsafe破坏单例
        //unsafe(Singleton1.class);
    }

    private static void serializable(Singleton1 instance) throws IOException, ClassNotFoundException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(instance);
        ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
        System.out.println("反序列化创建实例:" + objectInputStream.readObject());
    }

    private static void reflection(Class<?> clazz) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<?> constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        System.out.println("反射创建实例:" + constructor.newInstance());
    }

    private static void unsafe(Class<?> clazz) {
        /*Object o = UnsafeUtils.getUnsafe().allocateInstance(clazz);
        System.out.println("Unsafe创建实例:" + o);*/
    }
}

输出结果:
在这里插入图片描述
类似的,反序列化也会破坏单例,为避免反射和反序列化破坏单例,可以作如下修改:

public class Singleton1 implements Serializable {
    private Singleton1() {
        if (INSTANCE != null) {//防止反射破坏单例!
            throw new RuntimeException("单例对象不能重复创建");
        }
        System.out.println("private Singleton1");
    }

    private static final Singleton1 INSTANCE = new Singleton1();//给静态变量赋值的代码会放到静态代码块中,由JVM保证线程安全,我们不需要考虑线程安全问题。

    public static Singleton1 getInstance() {
        return INSTANCE;
    }//直接实例化

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

    //防止反序列化破坏单例
    public Object readResolve() {
        return INSTANCE;
    }

}

构造方法抛出异常是防止反射破坏单例。
readResolve()是防止反序列化破坏单例。
unsafe()破坏单例还没有办法避免。

懒汉式

import java.io.Serializable;

/**
 * @author Margaret
 * @create 2022-01-13 17:15
 */
//懒汉式
public class Singleton3 implements Serializable {
    private Singleton3() {
        System.out.println("private Singleton3()");
    }

    private static Singleton3 INSTANCE = null;

    //多线程下使用会不安全,在静态方法上加一个synchronized相当于给这个方法加了一把锁,可以保证方法的原子操作
    public static synchronized Singleton3 getInstance() {
        if (INSTANCE != null) {
            INSTANCE = new Singleton3();
        }
        return INSTANCE;
    }

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

其实只有首次创建单例对象时需要同步,但实际上每次调用都会同步,因此有了DCL改进。

DCL(双重检查锁)

//DCL  双检锁
public class Singleton4 implements Serializable {
    private Singleton4() {
        System.out.println("private Singleton4()");
    }

    private static volatile Singleton4 INSTANCE = null;//volatile修饰共享变量可以阻止指令重排序,保证指令的有序性

    public static void getInstance() {
        if (INSTANCE != null) {
            synchronized (Singleton4.class) {//synchronized前后都加上if判断
                if (INSTANCE != null) {
                    INSTANCE = new Singleton4();
                }
            }
        }
    }

}

之所以必须加 volatile的原因:

  • INSTANCE = new Singleton4() 不是原子的,分成 3 步:创建对象、调用构造、给静态变量赋值,其中后两步可能被指令重排序优化,变成先赋值、再调用构造
  • 如果线程1 先执行了赋值,线程2 执行到第一个 INSTANCE == null 时发现 INSTANCE 已经不为 null,此时就会返回一个未完全构造的对象

枚举饿汉式

//枚举
public enum Singleton2 {
    INSTANCE;

    private Singleton2() {
        System.out.println("private Singleton2()");
    }

    @Override
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

    public static Singleton2 getInstance() {
        return INSTANCE;
    }

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

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

内部类懒汉式

import java.io.Serializable;

/**
 * @author Margaret
 * @create 2022-01-13 22:08
 */
public class Singleton5 implements Serializable {
    private Singleton5() {
        System.out.println("private Singleton5()");
    }

    private static class Holder {
        static Singleton5 INSTANCE = new Singleton5();
    }

    public static Singleton5 getInstance() {
        return Holder.INSTANCE;
    }

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

内部类懒汉式避免了双检锁的缺点。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值