设计模式-单例模式(Java)

单例模式

核心作用:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。

常见的应用场景:

  • Windows的任务管理器。
  • Windows的回收站。
  • 在项目中,读取配置文件的类,一般也是只有一个对象,没有必要每次都去new对象读取。
  • 数据库连接池一般也是单例模式。
  • Servlet编程中的每个Servlet也是单例的。

优点:

  • 由于单例模式的类只生成一个实例对象,减少了系统性能的开销。
  • 单例模式可以在系统设置全局的访问点,优化共享资源访问。

常见的五种单例模式实现方式:

  • 饿汉式(线程安全,调用效率高,不能延时加载)
  • 懒汉式(线程安全,电泳效率不高,可以延时加载)
  • DCL懒汉式(由于JVM底层内部模型的原因,偶尔会出现问题,不建议使用)
  • 饿汉式改进:静态内部类式(线程安全,调用效率高,可延时加载)
  • 枚举单利(线程安全,调用效率高,不能延时加载)

饿汉式

//饿汉式单例
public class SingletonDemo01 {

    //缺点:当我们很久没有使用getInstance()方法的时候 也会开辟成员变量的空间,浪费空间
    private byte[] data1 = new byte[1024];

    //1.私有化构造器
    private SingletonDemo01() {
    }

    //2.类初始化的时候就立即加载
    private static SingletonDemo01 instance = new SingletonDemo01();

    //3.提供获取该对象的方法,没有synchronized,效率高
    public static SingletonDemo01 getInstance() {
        return instance;
    }
}
//测试类
class SingletonDemo01Test{
    public static void main(String[] args) {
        SingletonDemo01 instance = SingletonDemo01.getInstance();
        SingletonDemo01 instance1 = SingletonDemo01.getInstance();

        System.out.println(instance == instance1);//true
    }
}

这种方式实现也很简单,但是有个缺点,就是当我们很久都不使用该它的时候,成员变量的空间开辟了不会得到使用,就会造成空间的浪费,懒汉式就能对这问题进行解决。

懒汉式

//懒汉式单例
public class SingletonDemo02 {

    //1.私有化构造器
    private SingletonDemo02() {
    }

    //2.类初始化的时候,不立即加载该对象
    private static SingletonDemo02 instance;


    //3.提供获取该对象的方法,如果已加载就直接返回,未加载就先实例化,添加synchronized保证线程安全,效率较低
    public static synchronized SingletonDemo02 getInstance(){
        if (instance==null){
            instance = new SingletonDemo02();
        }
        return instance;
    }
}

class SingletonDemo02Test{
    public static void main(String[] args) {
        SingletonDemo02 instance = SingletonDemo02.getInstance();
        SingletonDemo02 instance1 = SingletonDemo02.getInstance();

        System.out.println(instance == instance1);//true
    }
}

由于在方法上添加了synchronized关键字,所以这种方式的效率就相对较低了,DCL懒汉式又是这种方式的优化。

DCL懒汉式

//DCL懒汉式(DCL:double click lock 双重检查锁)
public class SingletonDemo03 {
    //1.私有化构造器
    private SingletonDemo03() {
    }

    //2.类初始化的时候,不立即加载该对象 
    // 添加volatile保证有线程对该变量修改时,另一个线程读取时,缓存就失效了,直接从内存中读取
    private volatile static SingletonDemo03 instance;


    //3.提供获取该对象的方法,如果已加载就直接返回,未加载就先实例化
    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 instance = SingletonDemo03.getInstance();
        SingletonDemo03 instance1 = SingletonDemo03.getInstance();

        System.out.println(instance == instance1);//true
    }
}

这种方式看似没有任何问题,但是这不是原子性的操作,在极端的条件下,还是容易出现问题,就是多条线程如果同时进入new对象的操作前后,就极有可能破坏单例模式。

静态内部类

//静态内部类实现
public class SingletonDemo04 {

    //1.私有化构造器
    private SingletonDemo04(){
    }

    //2.通过静态内部类创建单例对象
    //当该内部类被调用时,静态内部类才加载
    private static class InnerClass{
        //通过static保证只有一份 final保证不能修改
        private static final SingletonDemo04 instance = new SingletonDemo04();
    }

    //3.提供外部访问方法
    public static SingletonDemo04 getInstance(){
        return InnerClass.instance;
    }

}

class SingletonDemo04Test {
    public static void main(String[] args) {
        SingletonDemo04 instance = SingletonDemo04.getInstance();
        SingletonDemo04 instance1 = SingletonDemo04.getInstance();

        System.out.println(instance == instance1);//true
    }
}

虽然说这种方式能够解决上面的问题,但是如果我们使用反射,就能忽略private关键字,从而实现通过反射破坏该单例模式。

SingletonDemo04 instance = SingletonDemo04.getInstance();
SingletonDemo04 instance1 = SingletonDemo04.getInstance();

System.out.println(instance == instance1);//true

//反射创建
Class<SingletonDemo04> singletonDemo04Class = SingletonDemo04.class;
Constructor<SingletonDemo04> constructor = singletonDemo04Class.getDeclaredConstructor(null);

//关闭安全检查
constructor.setAccessible(true);
SingletonDemo04 instance2 = constructor.newInstance(null);
System.out.println(instance1 == instance2);//false

枚举单例

//枚举单例
public enum SingletonDemo05 {
    INSTANCE;
}

class SingletonDemo05Test{
    public static void main(String[] args) {
        SingletonDemo05 instance = SingletonDemo05.INSTANCE;
        SingletonDemo05 instance1 = SingletonDemo05.INSTANCE;
        System.out.println(instance == instance1);//true
    }
}

枚举是不能被反射的,而且枚举是天然的单例,线程安全,调用效率高,枚举单例在目前来说还是比较推荐的一种方式。只是枚举单例不能实现延时加载。

以上五种方式都要根据实际情况来实际选择。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值