单例模式多重解析

1. 简介

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

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

2. 单例模式的几种实现方式

1.饿汉式

1.1 实现方式
/**
 * 饿汉式单例
 * 优点: 执行效率高
 * 缺点: 类加载时就初始化了,降低了内存使用效率
 */
public class Hungry {

    //构造器私有,该类就不能实例化
    private Hungry(){}
		//类加载时就初始化
    private static Hungry hungry = new Hungry();
		//外部获得该对象实例的方法
    public static Hungry getInstance(){
        return hungry;
    }
}
  • 是否多线程安全: 是
  • 是否懒加载:否
1.2 利用反射破坏单例

但是利用反射还是可以破坏单例模式

public class TestHungry{
		public static void main(String[] args){
      	Hungry instance = Hungry.getInstance();
        Constructor<hungry> declaredConstructor = Hungry.class.getDeclaredConstructor(null);
      	declaredConstructor.setAccessible(true);
      	Hungry instance1 = declaredConstructor.newInstance();
      	System.out.println(instance); 	//com.singleton.version1.Hungry@29453f44
        System.out.println(instance1); //com.singleton.version1.Hungry@5cad8086
      	
    }
}

2.懒汉式

2.1 简单懒汉式(线程不安全)

因为饿汉式在类加载时就创建了,占用内存空间,所以在饿汉式的基础上稍加修改

/**
 * 懒汉式(该版本严格意义上说算不上单例模式)
 * 优点: 在需要时创建实例
 * 缺点: 线程不安全
 */
public class LazyMan {
    //私有化构造器
    private LazyMan(){//为了方便测试查看,我们在创建该对象的时候可以清楚看见是否被实例化
      	System.out.println(Thread.currentThread().getName());
    }
    //按需创建单例
    private static LazyMan lazyMan = null;
    
    public LazyMan getInstance(){
        if(lazyMan==null){
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }
}

我们来测试一下线程是否安全

public class TestLazy {
    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazyMan.getInstance();
            }).start();
        }

    }
}
/*
Thread-5
Thread-0
Thread-1
Thread-8				可以发现线程不安全
Thread-6
Thread-3
Thread-2
Thread-7
Thread-4
*/
2.2 进阶版懒汉式(线程安全)

基础懒汉式线程不安全,因为多线程所以需要加锁 synchronized,锁谁呢,我们先试着锁 getInstance 方法

/**
 * 懒汉式(锁获得该类实例的方法)
 * 优点: 第一次调用才初始化,避免了内存浪费
 * 缺点: 必须加锁才能保证单例,多个线程同时访问时,效率很低
 */
public class LazyMan {
    //私有化构造器
    private LazyMan(){//为了方便测试查看,我们在创建该对象的时候可以清楚看见是否被实例化
      	System.out.println(Thread.currentThread().getName());
    }
    //按需创建单例
    private static LazyMan lazyMan = null;
    
    public synchronized LazyMan getInstance(){
        if(lazyMan==null){
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }
}

多个线程访问getInstance()方法时,只有一个线程才可以获得这个锁,其他线程都需要等待才可以执行,效率很低

2.3 DCL懒汉式(Double-checked locking)

DCL:双重检测锁, 此次锁的是该类的 class 对象,当执行方法时,先判断单例对象是否为 null,是则锁住该 class 对象,再判断一次锁住后该单例对象是否为空,若为空初始化单例

public class LazyMan {
  	private static LazyMan lazyMan = null;
  	private LazyMan(){}
  	public static LazyMan getInstance(){
      	if(lazyMan == null){
          	synchronized(LazyMan.class){
              	if(lazyMan == null){
										lazyMan = new LazyMan();
                }
            }
        }
      	return lazyMan;
    }
}

但是初始化时, new LazyMan() 这条指令不是原子性操作,可能会产生指令,一般状态下的过程为:

  1. 在堆内存分配内存空间
  2. 初始化
  3. 把对象指向该内存空间

可能会发生指令重排 执行顺序为 1 -> 3 -> 2,导致判断 lazyMan==null 为假,此时lazyMan还没有完成构造。

volatile可以保证原子性操作,所以最终 DCL 懒汉式代码如下:

public class LazyMan {
  	private static LazyMan lazyMan = null;
  	private LazyMan(){}
  	public volatile static LazyMan getInstance(){
      	if(lazyMan == null){
          	synchronized(LazyMan.class){
              	if(lazyMan == null){
										lazyMan = new LazyMan();
                }
            }
        }
      	return lazyMan;
    }
}

3. 静态内部类


public class Singleton {
    private static class SingletonHolder{
        private static final Singleton instance = new Singleton();

    }

    private Singleton(){}

    public static final Singleton getInstance(){
        return SingletonHolder.instance;
    }
}

这种方式能达到和双重检测锁同样的效果,但是实现方法更简单.

这种方式只适用于静态域的情况,双检锁方式可以在实例域需要延迟初始化时使用

利用了 classloader 机制来保证初始化 instance 只有一个线程. 跟饿汉式方式不同的是:

  1. 饿汉式只要类被加载了,那么 instance 就会被实例化,

  2. 而该方式,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance

4. 枚举

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

3.几种实现方式的对比

3.1 饿汉式与懒汉式

饿汉式简单方便实现,但是类被加载时就创建,所以降低了内存利用率。

懒汉式运用了 DCL 机制,保证了唯一性,但是加锁导致多线程访问时效率较低

3.2 饿汉式与静态内部类

饿汉式和静态内部类都保证了唯一性,而且都是类加载时创建,不同的是

饿汉式是类被加载时创建,而静态内部类没有显式访问静态内部类,则该单例实例不会被创建。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值