Java设计模式-单例模式(多种实现)

设计模式-单例模式

单例模式(Singleton,也叫单子模式,是一种常用的设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候,整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,显然,这种方式简化了在复杂环境下的配置管理。

简单来说使用单例模式可以带来下面几个好处:

  • 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
  • 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。

综上所述,单例模式就是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种方法。

懒汉式

懒汉式,顾名思义就是实例在用到的时候才去创建,“比较懒”,用的时候才去检查有没有实例,如果有则返回,没有则新建。有线程安全和线程不安全两种写法,区别就是synchronized关键字。

线程不安全

public class Singleton{
    private Singleton(){};
    private static Singleton singleton;
    public static Singleton getIntance(){
        if(singleton==null){
            return new Singleton();
        }
    }
}

线程安全(synchronized)

在程序中每次使用getInstance()都要经过synchronized加锁这一层,这难免会增加getInstance()的方法的时间消费,而且还可能会发生阻塞

public class Singleton{
    private Singleton(){};
    private static Singleton singleton;
    //加入synchronized 关键字
    public static synchronized Singleton getIntance(){
        if(singleton==null){
            return new Singleton();
        }
    }
}

synchronized加在方法上,使得每次获得实例都要同步,开销很大,性能很低。强烈不推荐这种方式实现单例。

线程安全(synchronized代码块)

public class Singleton{
    private Singleton(){};
    private static Singleton singleton;
    public static Singleton getIntance(){
        synchronized(){
          if(singleton==null){
            return new Singleton();
        }  
        }  
    }
}

线程安全(双重检查加锁版本)

这种方式相比于使用synchronized关键字的方法,可以大大减少getInstance()的时间消费。

public class Singleton {

    //volatile保证,当uniqueInstance变量被初始化成Singleton实例时,多个线程可以正确处理uniqueInstance变量
    private volatile static Singleton uniqueInstance;
    private Singleton() {
    }
    public static Singleton getInstance() {
       //检查实例,如果不存在,就进入同步代码块
        if (uniqueInstance == null) {
            //只有第一次才彻底执行这里的代码
            synchronized(Singleton.class) {
               //进入同步代码块后,再检查一次,如果仍是null,才创建实例,防止多线程都通过第一次校验
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

instance 成员变量加了volatile 关键字修饰,是为了防止指令重排,因为instance = new DoubleCheckIdGenerator();并不是一个原子操作,其在JVM中至少做了三件事:

  1. 给instance在堆上分配内存空间。(分配内存)

  2. 调用DoubleCheckIdGenerator的构造函数等来初始化instance。(初始化)

  3. 将instance对象指向分配的内存空间。(执行完这一步instance就不是null了)

    在没有volatile修饰时,执行顺序可以是1,2,3,也可以是1,3,2。假设有两个线程,当一个线程先执行了3,还没执行2,此时第二个线程来到第一个check,发现instance不为null,就直接返回了,这就出现问题,这时的instance并没有完全完成初始化。

听说高版本的 Java 已经在 JDK 内部实现中解决了这个问题(解决的方法很简单,只要把对象 new 操作和初始化操作设计为原子操作,就自然能禁止重排序)。但是并没有在官方资料中看到,所以以防万一,还是加上volatile 这个关键字。

饿汉式

线程安全

public class Singleton{
    private Singleton(){};
    private static Singleton singleton = new Singleton();
    public static Singleton getIntance(){
            return singleton;
    }
}

线程安全(内部类)

public class Singleton{
    private Singleton(){};
    private static class SingletonHolder{
        private static final Singleton singleton = new Singleton();
    }
    
    public static Singleton getInstance(){
        return SingletonHolder.singleton; 
    }
}

SingletonHolder是一个静态内部类,当外部类 StaticInnerClassIdGenerator被加载的时候,并不会创建 SingletonHolder实例对象。只有当调用 getInstance()方法时,SingletonHolder 才会被加载,这个时候才会创建 instance。

instance 的唯一性、创建过程的线程安全性,都由 JVM来保证。所以,这种实现方法既保证了线程安全,又能做到延迟加载。

枚举(最好的实现方式)

public class EnumSingleton {

    public static EnumSingleton getInstance() {
        return Elvis.INSTANCE.getInstance();
    }

    private enum Elvis {
        INSTANCE;
        private EnumSingleton singleton;
		//构造方法
        Elvis() {
            singleton = new EnumSingleton();
        }

        private EnumSingleton getInstance() {
            return singleton;
        }
    }
}

//外部调用
EnumSingleton enumSingleton = EnumSingleton.getInstance();

饿汉式以及懒汉式中的双重检查式、静态内部类式都无法避免被反序列化和反射生成多个实例。而枚举方式实现的单例模式不仅能避免多线程同步的问题,也可以防止反序列化和反射的破坏。

枚举单例模式具有以下三个优点:

  • 写法简洁,代码短小精悍。
  • 线程安全。
  • 防止反序列化和反射的破坏。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值