设计模式2-单例模式

设计模式2-单例模式


一、什么是单例模式?

单例模式:只能创建一个实例化对象,属于创建型模式
注:该类私有构造方法;
该类只能自己创建自己的实例;

实现方式有5种,分别是恶汉模式、懒汉模式,双检锁/双重校验锁,匿名内部类和枚举;

二、实现方式

1.恶汉模式

如同恶汉一样,类初始化同时创建类的实例,由于类的加载机制,在这个类的生命周期只被加载一次,所以保证单例。
优点:不存在线程安全问题。
缺点:创建的类如果没被使用,就会造成内存的浪费

public class Singleton {
   //单例模式-恶汉
   private static Singleton instance = new Singleton();
   private Singleton() {    //私有构造方法
    }
   public static Singleton getInstance(){
          return instance;
   }
}

2.懒汉模式

只有当自己需要的时候才创建对象。很好的解决内存浪费问题,但是会产生线程安全的问题。

public class Singleton {
   //单例模式-懒汉
   private static Singleton instance =null;
   private Singleton() {    //私有构造方法
    }
   public static Singleton getInstance(){
			if(instance == null){  //1 
			instance = new Singeleton();  //2
			}
          return instance;
   }
}

当线程A和线程B同时进来,走到1的位置,由于都没有初始化实例,所以instace == null 结果是true,这时在2的位置都会实例化对象。


3.双检锁/双重校验锁机制

由于懒汉模式出现线程安全问题,所以需要通过锁机制来解决这个问题。即在方法上加上synchronized关键字。

    public synchronized static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

但是这样会大大降低性能,每次进来都先进行加锁,线程越多,性能会越来越慢的。所以采用双重锁机制。

    public  static Singleton getInstance() {
        if (instance == null) { //1
            synchronized(Singleton.class){ //2
                if (instance == null) { //3
                    instance = new Singleton(); //4
                }
            }
        }
        return instance;
    }

1处的判断:是当第一次实例化对象之后,则直接使用之前的对象,没必要执行后面的代码,大大提高了效率
2处判断:当线程A和线程B同时经过1,线程A先获得cpu资源,线程B在2处堵塞,当线程A实例化完成后并释放锁资源,线程B获得锁,此时instance不为空,所以又会实例化一次,所以需要在同步锁里进行判断instance是否为空。
但是上面这段代码也不是绝对线程安全的,这是由于jvm指令重排的原因,我们看一下这段代码,jvm实际帮我们做了什么:

instance = new Singleton()

1. 在堆空间开辟内存空间;
2. 初始化对象和里面的各个参数;
3. 将instance指向对空间的内存地址

这里由于jvm指令重排的原因,可能执行顺序变成1->3->2,当执行执行到第三步的时候,instance不为空了,但并未初始化。
当线程A正在执行4处时,先执行的第三步,此时线程B走到1处判断,由于instance已经不为空了,所以返回未初始化的instance。所以为了解决这个问题,jdk1.6以后引用volatitle。volatitle禁止jvm指令重排,保证了顺序,就不会出现上述问题。

  private volatile static Singleton instance = null;  //单例对象

4.静态内部类

这种方式既可以解决线程安全的问题,又能解决内存浪费的问题,利用了类的加载机制。
静态内部类,在类初始化的时候不会初始化静态内部类,只有当调用的时候才会,所以不占用内存,只有调用getInstance()方法时才会加载LazyHolder 类,而且之后调用不会加载。
jvm会保证一个类的初始化方法会在多线程环境中正确加锁,同步,如果多个线程去初始化同一个类,只有一个线程执行初始化方法,其他线程会阻塞,注意当一个线程执行完毕初始化方法后,其他线程被唤醒也不会执行初始化方法了,一个类只会初始化一次。
这种也不是最完美的,无法完成传参。

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

5.枚举单例

类名.字段调用,线程安全

public enum Singleton2 {
    INSTANCE;
    public void method(){

    }

    public static void main(String[] args) {
        Singleton2 instance = Singleton2.INSTANCE;
        Singleton2 instance1 = Singleton2.INSTANCE;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值