经典设计模式之单例模式

一、什么是单例模式

单例模式确保一个类只有一个实例,并提供一个全局访问点,实现单例模式的方法是私有化构造函数,通过getInstance()方法实例化对象,并返回这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。

二、单例模式的特点

  1. 单例类只能有一个实例。
  2. 单例类必须自己创建自己唯一的实例。
  3. 单例类必须给所有其它对象提供这一实例。

三、单例模式的优缺点

优点:

  1. 单例类只有一个实例
  2. 共享资源,全局使用
  3. 节省创建时间,提高性能

缺点:可能存在线程不安全问题。

四、单例模式的七种写法

分别是「饿汉」、「懒汉(非线程安全)」、「懒汉(线程安全)」、「双重校验锁」、「静态内部类」、「枚举」和「容器类管理]

1、饿汉式
public class SingletonV1 {
    /**
     * 饿汉式:
     * 优点:先天线程安全。当类被加载的时候,就会创建对象。
     * 缺点:1、如果项目中使用过多的饿汉式会发生问题,项目在启动的时候会变的很慢。
     *      2、singletonV1对象是static的,所以会存放在方法区里面,占用内存大。
     *      3、如果用户不使用该对象,也会被提前创建好,比较浪费资源。
     */
    private static SingletonV1 singletonV1 = new SingletonV1();

    /**
     * 将构造函数私有化,禁止初始化
     */
    private SingletonV1(){}

    public static SingletonV1 getInstance(){
        return singletonV1;
    }

    public static void main(String[] args) {
        SingletonV1 v1 = SingletonV1.getInstance();
        SingletonV1 v2 = SingletonV1.getInstance();
        //结果为true
        System.out.println(v1 == v2);
    }
}
java.lang.Runtime源码分析:
public class Runtime {	
	private static Runtime currentRuntime = new Runtime();
    
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    private Runtime() {}
}
2、懒汉式(线程不安全)
public class SingletonV2 {
    //懒汉式,线程不安全
    private static SingletonV2 singletonV2;

    private SingletonV2(){}

    public static SingletonV2 getInstance(){
        if (singletonV2 == null) {
            try {
                Thread.sleep(2000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            singletonV2 = new SingletonV2();
        }
        return singletonV2;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                public void run() {
                    SingletonV2 instance2 = SingletonV2.getInstance();
                    System.out.println(Thread.currentThread().getName()+" - "+instance2);
                }
            }).start();
        }
    }
}
结果:线程不安全
Thread-2 - com.xwhy.singleton.v2.SingletonV2@1dda492b
Thread-13 - com.xwhy.singleton.v2.SingletonV2@46360c95
Thread-5 - com.xwhy.singleton.v2.SingletonV2@4e1de1c0
Thread-0 - com.xwhy.singleton.v2.SingletonV2@64403094
Thread-11 - com.xwhy.singleton.v2.SingletonV2@4a86c08f
Thread-12 - com.xwhy.singleton.v2.SingletonV2@5824138f
Thread-24 - com.xwhy.singleton.v2.SingletonV2@66b01c61
Thread-31 - com.xwhy.singleton.v2.SingletonV2@17733f02
Thread-44 - com.xwhy.singleton.v2.SingletonV2@579ade96
Thread-43 - com.xwhy.singleton.v2.SingletonV2@60fe2295
Thread-6 - com.xwhy.singleton.v2.SingletonV2@1b4b1c5b
Thread-49 - com.xwhy.singleton.v2.SingletonV2@1f904935
Thread-3 - com.xwhy.singleton.v2.SingletonV2@4e1de1c0
...
3、懒汉式(线程安全)
public class SingletonV3 {
    //懒汉式,线程不安全
    private static SingletonV3 singletonV3;

    private SingletonV3(){}

    /**
     * 效率低
     * @return
     */
    public static synchronized SingletonV3 getInstance(){
            try {
                Thread.sleep(2000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        if (singletonV3 == null) {
            System.out.println("创建实例singletonV3");
            singletonV3 = new SingletonV3();
        }
        System.out.println("获取实例singletonV3");
        return singletonV3;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                public void run() {
                    SingletonV3 singletonV3 = SingletonV3.getInstance();
                    System.out.println(Thread.currentThread().getName()+" - "+singletonV3);
                }
            }).start();
        }
    }
}
结果:
创建实例singletonV3
获取实例singletonV3
Thread-0 - com.xwhy.singleton.v3.SingletonV3@470bc213
获取实例singletonV3
Thread-99 - com.xwhy.singleton.v3.SingletonV3@470bc213
获取实例singletonV3
Thread-98 - com.xwhy.singleton.v3.SingletonV3@470bc213
获取实例singletonV3
Thread-97 - com.xwhy.singleton.v3.SingletonV3@470bc213
获取实例singletonV3
...
4、双重检验锁(DCL)
/*
 * 解决懒汉式获取对象的效率问题
 */
public class SingletonV4 {
    /**
     * volatile 禁止指令重排序,增加可见性
     */
    private volatile static SingletonV4 singletonV4;

    private SingletonV4(){}

    public static SingletonV4 getInstance(){
        //第一次判断如果没有创建对象就开始加锁
        if(singletonV4 == null){
            //当用户抢到锁,判断初始化
            synchronized (SingletonV4.class){
                if(singletonV4 == null){
                    singletonV4 = new SingletonV4();
                }
            }
        }
        return singletonV4;
    }

    /**
     * 测试单例
     */
    public static void main(String[] args) {
        for (int i=0;i<100;i++){
            new Thread(new Runnable() {
                public void run() {
                    SingletonV4 instance4 = SingletonV4.getInstance();
                    System.out.println(Thread.currentThread().getName()+","+instance4);
                }
            }).start();
        }
    }
}
结果:线程安全
Thread-1,com.xwhy.singleton.v4.SingletonV4@64403094
Thread-4,com.xwhy.singleton.v4.SingletonV4@64403094
Thread-5,com.xwhy.singleton.v4.SingletonV4@64403094
Thread-2,com.xwhy.singleton.v4.SingletonV4@64403094
Thread-3,com.xwhy.singleton.v4.SingletonV4@64403094
Thread-7,com.xwhy.singleton.v4.SingletonV4@64403094
Thread-0,com.xwhy.singleton.v4.SingletonV4@64403094
Thread-8,com.xwhy.singleton.v4.SingletonV4@64403094
Thread-6,com.xwhy.singleton.v4.SingletonV4@64403094
Thread-9,com.xwhy.singleton.v4.SingletonV4@64403094
Thread-10,com.xwhy.singleton.v4.SingletonV4@64403094
....
5、静态内部类
public class SingletonV5 {
    private SingletonV5(){
        System.out.println("构造函数执行...");
    }

    /**
     * 静态内部类特征:继承懒汉式和饿汉式优点,同时解决双重检验锁的第一次加载慢的问题
     * 读和写都不需要同步所以效率非常高
     * 内部类被调用的时候才会去初始化singletonV5
     */
    private static class SingletonHolder{
        private static final SingletonV5 singletonV5 = new SingletonV5();
    }

    public static SingletonV5 getInstance(){
        return SingletonHolder.singletonV5;
    }

    public static void main(String[] args) {
        System.out.println("项目启动成功。。。");
        SingletonV5 instance1 = SingletonV5.getInstance();
        SingletonV5 instance2 = SingletonV5.getInstance();
        System.out.println(instance1==instance2);
    }
}
结果:
项目启动成功。。。
对象初始化...
true
6、枚举形式
public enum  SingletonEnum {
    INSTANCE;
    // 枚举能够绝对有效的防止实例化多次,和防止反射和序列化破坏
    public void add() {
        System.out.println("add方法...");
    }
    /**
     * 测试单例
     */
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        SingletonEnum instance1 = SingletonEnum.INSTANCE;
        SingletonEnum instance2 = SingletonEnum.INSTANCE;
        System.out.println(instance1==instance2);
        Constructor<SingletonEnum> declaredConstructor = SingletonEnum.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        SingletonEnum v6 = declaredConstructor.newInstance();
        System.out.println(v6==instance1);

    }
}
输出结果:
true
Exception in thread "main" java.lang.NoSuchMethodException: com.xwhy.singleton.v6.SingletonEnum.<init>()
	at java.lang.Class.getConstructor0(Class.java:3082)
	at java.lang.Class.getDeclaredConstructor(Class.java:2178)
	at com.xwhy.singleton.v6.SingletonEnum.main(SingletonEnum.java:24)

枚举反射破解不了单例

7、使用容器管理
public class SingletonManager {
    private static Map<String, Object> objMap = new HashMap<String, Object>();
    public static void registerService(String key, Object instance) {
        if (!objMap.containsKey(key)) {
            objMap.put(key, instance);
        }
    }
    public static Object getService(String key) {
        {
            return objMap.get(key);
        }
    }
}

这种使用SingletonManager 将多种单例类统一管理,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。

五、如何防止破坏单例

虽然单例通过私有构造函数,可以实现防止程序员初始化对象,但是还可以通过反射和序列化技术破坏单例。

1、使用反射技术破坏单例
// 1. 使用懒汉式创建对象
SingletonV3 instance1 = SingletonV3.getInstance();
// 2. 使用Java反射技术初始化对象 执行无参构造函数
Constructor<SingletonV3> declaredConstructor = SingletonV3.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
SingletonV3 instance2 = declaredConstructor.newInstance();
System.out.println(instance1 == instance2);
如何防止被反射破坏
私有构造函数
private SingletonV3() throws Exception {
    synchronized (SingletonV3.class) {
        if (singletonV3 != null) {
            throw new Exception("该对象已经初始化..");
        }
        System.out.println("执行SingletonV3无参构造函数...");
    }
}
2、使用序列化技术破坏单例
Singleton instance = Singleton.getInstance();
FileOutputStream fos = new FileOutputStream("E:\\code\\Singleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance);
oos.flush();
oos.close();

FileInputStream fis = new FileInputStream("E:\\code\\Singleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
Singleton singleton2 = (Singleton) ois.readObject();
System.out.println(singleton2==instance)

//返回序列化获取对象 ,保证为单例
public Object readResolve() {
    return singletonV3;
}

六、枚举单例为什么不能够反射初始化

枚举底层原理

1、首先如果使用java反射机制破坏单例,报错
枚举反射报错
通过该错误说明,枚举类中没有无参的构造函数

2、使用java反编译技术,查看枚举类
枚举类反编译
从该图可以看出,枚举底层其实是一个类

3、枚举类底层原理分析
使用静态代码块的方式,当静态代码块执行当时候初始化对象,从而可以让开发者可以通过EnumSingleton.INSTANCE使用。静态代码块
4、在该反编译源码中,定义了一个类继承了Enum 该类是中没有无参构造函数,所以反射机制调用无参构造函数是无法初始化的。

5、在该类中有一个只有一个有参构造函数
有参构造函数
调用父类构造构造函数
父类构造函数
Name 为定义调用对象名称,定义调用对象序号ordinal

6、使用注入有参构造函数是否可以破坏枚举呢?也不行的。

Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
EnumSingleton v3 = declaredConstructor.newInstance();
System.out.println(v3 == instance1);

报错信息
为什么报这个错误呢?主要原因是 java的反射初始化对象中,只要对象是枚举类型是不会初始化的。源代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值