23种设计模式——单例模式

单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

注意:

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

介绍

  • 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  • 主要解决:一个全局使用的类频繁地创建与销毁。
  • 何时使用:当您想控制实例数目,节省系统资源的时候。
  • 如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
  • 关键代码:构造函数是私有的。

实现

1. 单例模式之饿汉式单例

不支持多线程,线程不安全,这种方式比较常用,但容易产生垃圾对象。

//饿汉式单例

/**
 * 优点,没有synchronized,效率高,不能延迟加载
 * 缺点,如果类中有 private byte[] bytes1=new byte[1024];
 *      这样占用内存资源的对象,又不会用,会占用内存资源,造成浪费。
 *适用于,类中没有开辟空间内存的对象。如:spring
 */
public class SingletonDemo01 {

//    private byte[] bytes1=new byte[1024];
//    private byte[] bytes2=new byte[1024];
//    private byte[] bytes3=new byte[1024];
//    private byte[] bytes4=new byte[1024];
//    private byte[] bytes5=new byte[1024];

    //私有化构造器
    private SingletonDemo01(){}
    //类初始化时候,立即加载该对象
    private static SingletonDemo01 instance=new SingletonDemo01();
    //提供获取该对象的方法,没有synchronized,效率高
    public static  SingletonDemo01 getInstance(){
        return instance;
    }
}

class SingletonDemo01Test{
    public static void main(String[] args) {
        SingletonDemo01 instance1=SingletonDemo01.getInstance();
        SingletonDemo01 instance2=SingletonDemo01.getInstance();
        System.out.println(instance1==instance2);
        System.out.println(instance1.hashCode());
        System.out.println(instance1.hashCode());
    }
}

运行结果:
在这里插入图片描述

2. 单例模式之懒汉式单例

支持多线程,线程安全,这种方式比较常用,有延迟加载。

package com.per.singleton;

/**
 * 懒汉式单例
 * 优点,有synchronized,效率低,但是有延迟加载
 * 缺点,极端情况不安全。
 *适用于,类中没有开辟空间内存的对象。如:spring
 */
public class SingletonDemo02 {

    //私有化构造器
    private SingletonDemo02(){}
    //类初始化时候,立即加载该对象
    private static SingletonDemo02 instance;
    //提供获取该对象的方法,有synchronized,效率低
    public static synchronized SingletonDemo02 getInstance(){
        if(instance==null){
            instance=new SingletonDemo02();
        }
        return instance;
    }
}


class SingletonDemo02Test{
    public static void main(String[] args) {
        SingletonDemo02 instance1=SingletonDemo02.getInstance();
        SingletonDemo02 instance2=SingletonDemo02.getInstance();
        System.out.println(instance1==instance2);
        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
         }
}

运行结果:

在这里插入图片描述

3.单例模式之 双检锁/双重校验锁(DCL,即 double-checked locking)

在懒汉式基础上进行改进

package com.per.singleton;

//DOL(双锁)懒汉式单例

/**
 * 优点,相较于懒汉式单例效率更高了
 * * volatile,关键字的作用:保证了变量的可见性(visibility)。
 * * 被volatile关键字修饰的变量,如果值发生了变更,其他线程立马可见,避免出现脏读的现象
 *
 * 缺点,当一个线程还没走完同步锁,另一个线程进来会直接返回return instance;这是出现错误
 */
public class SingletonDemo03 {
    private SingletonDemo03() {
    }

    //类初始化时候,立即加载该对象
    //volatile,关键字的作用:保证了变量的可见性(visibility)。
    private static  SingletonDemo03 instance;

    //提供获取该对象的方法,有synchronized,效率低
    public static SingletonDemo03 getInstance() {
        if (instance == null) {
            synchronized (SingletonDemo03.class) {
                if (instance == null) {
                    instance = new SingletonDemo03();
                }
            }
        }
        return instance;
    }
}

class SingletonDemo03Test{
    public static void main(String[] args) {
        SingletonDemo03 instance1=SingletonDemo03.getInstance();
        SingletonDemo03 instance2=SingletonDemo03.getInstance();
        System.out.println(instance1==instance2);
        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());

    }
}

运行结果:
在这里插入图片描述

但是这种模式还是不太安全,因为当一个线程还没走完同步锁,另一个线程进来会直接返回 return instance;这时出现错误。
所以这是把private static SingletonDemo03 instance;改为private volatile static SingletonDemo03 instance;

  • volatile,关键字的作用:保证了变量的可见性(visibility)。 被volatile关键字修饰的变量,如果值发生了变更,其他线程立马可见,避免出现脏读的现象

4. 单例模式之 登记式/静态内部类

package com.per.singleton;

import java.lang.reflect.Constructor;

/**
 * 单例模式进化到第四个版本是不是感觉已经很完善了,
 * 
 */
public class SingletonDemo04 {
    private SingletonDemo04(){}
    private static class Inner{
        private static SingletonDemo04 instance=new SingletonDemo04();
    }

    public static SingletonDemo04 getInstance(){
        return Inner.instance;
    }
}

class SingletonDemo04Test{
    public static void main(String[] args) throws Exception {
    SingletonDemo04 instance1=SingletonDemo04.getInstance();
    SingletonDemo04 instance2=SingletonDemo04.getInstance();
    System.out.println(instance1==instance2);
    System.out.println(instance1.hashCode());
    System.out.println(instance2.hashCode());
         }
}

运行结果:

在这里插入图片描述

这时你是否会认为足够安全,答案是否定的。
在Java中但是有一个很霸道的东西,那就是反射,反射机制会破环这种单例。

例如:我们进行这样的测试:

class SingletonDemo04Test{
    public static void main(String[] args) throws Exception {
        SingletonDemo04 instance1=SingletonDemo04.getInstance();
        Constructor<SingletonDemo04> declaredConstructor = SingletonDemo04.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);//关闭权限检查。private SingletonDemo03(){} 中private失效。
        SingletonDemo04 instance2 = declaredConstructor.newInstance();
        System.out.println(instance1==instance2);
        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }
}

运行结果:
在这里插入图片描述

这是再看结果,为false,说明反射已经破环了我们的单例。

如何解决呢?

第一种解决反射方案:我们已DCL单例为例。在私有构造器里面加上同步代码块。

private SingletonDemo03() {
    synchronized (SingletonDemo06.class){
        if(instance!=null){
           throw  new RuntimeException("不要试图用反射破环单例");
        }
    }
}

这时结果为:

在这里插入图片描述

反射不会在破环我们的单例。
但是,这种情况是我们先进行的初始化操作,所以反射就不能使用,但是如果先进行反射呢?
答案肯定就是可以的。如何解决呢?

第二种解决反射方案:在类中加入标志位。

private static boolean falg = false;
private SingletonDemo03() {
    if (falg == false) {
        falg = true;
    } else {
        throw new RuntimeException("不要试图用反射破环单例");
    }
}
  • 测试代码
class SingletonDemo03Test {
    public static void main(String[] args) throws Exception {

        Constructor<SingletonDemo03> declaredConstructor = SingletonDemo03.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);//关闭权限检查。private SingletonDemo03(){} 中private失效。
        SingletonDemo03 instance2 = declaredConstructor.newInstance();
        SingletonDemo03 instance1 = declaredConstructor.newInstance();
        System.out.println(instance1 == instance2);
        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());

    }
}

这时结果为:

在这里插入图片描述

这种解决方案就解决了刚才的问题。但是这种方案还不是最安全的,因为在反射中 declaredConstructor.setAccessible(true);//关闭权限检查。private SingletonDemo03(){} 中private失效。 可以在外部设置你的标志位。

但是在反射中枚举类型就不可以被反射。

5.单例模式之 枚举

在这里插入图片描述

从源码中看出枚举类型是不可以反射的。

代码:

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

是不是感觉很简单,

描述:这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
不能通过 reflection attack 来调用私有构造方法。

总结:一般情况下,不建议使用第 2 种懒汉方式,建议使用第1 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 4 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 5 种枚举方式。如果有其他特殊的需求,可以考虑使用第 3 种双检锁方式。

设计模式持续更新中......

发布了18 篇原创文章 · 获赞 4 · 访问量 1071

猜你喜欢

转载自blog.csdn.net/weixin_44689277/article/details/104440057