设计模式-单例模式5种方式(饿汉、懒汉、双重锁、静态内部类、枚举)

单例模式

单例模式主要有5种模式:

  • 饿汉模式:实例在初始化就已经创建好对象。
  • 懒汉模式:在使用实例时进行判断,若不为null则创建对象,否则直接调用。
  • 双重检查:通过锁来双重判断实例是否为null。
  • 静态内部类:通过静态内部类去实例化对象。
  • 枚举:JDK1.5版本后提供了枚举,天然支持单例模式。

1.饿汉模式

实例在初始化就已经创建好对象,不管有没有使用都会创建,会造成资源浪费。

public class Main {
    
    private static Main instance = new Main();

    private Main(){}

    public static Main getInstance() {
        return instance;
    }
}

2.懒汉模式

实现了懒加载,但是在并发情况下会有多个线程同时进入if条件中,会产生多个实例。

public class Main {

    private static Main instance;

    private Main(){}

    public static Main getInstance() {
        if (instance != null)
            instance = new Main();
        return instance;
    }
}

3.双重检查

在获取实例时加锁进行双重判断来创建实例。

// 双重检查模式的懒汉单例模式
public class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        // 若对象为空,在多线程的访问下,多个线程会同时进入该if语句。若对象不为空,直接返回,不用进去排队
        if (instance == null) {
         // 对多线程进行排队,防止多次初始化
         synchronized (Singleton.class) {
             // 对单个线程进行判断,是否要初始化
             if (instance == null) {
                 instance = new Singleton();                 
             }             
         }         
        }   
        return instance;
    }
}

注意:

以上代码可能会出现问题,因为instance = new Singleton()不是一个原子操作,会在执行时拆分成一下几个动作:

  1. 先进行new操作,JVM会分配内存地址、内存空间。
  2. 然后使用构造方法实例化对象。
  3. 最后再进行赋值,instance = 分配好的内存地址(引用保存的对象地址)。

重排序问题:

因为以上3个动作在真正执行时可能是1、2、3顺序,也可能1、3、2顺序,单线程下不影响结果,但多线程下使用1、3、2可能出现的问题:

  1. 假设A线程执行完1、3步骤(2还没有执行)。
  2. B线程进入到单例模式的if语句中,会直接得到Instance对象(在没有执行2步骤下),这时若使用该对象(instance.xxx())将会必然报错。

解决:Volatile关键字

在对象变量中加上volatile关键字如下:

private volatile static Singleton instance = null;

4.静态内部类

  • 采用了类加载的机制保证实例化只有一个线程。
  • 只会在第一次主动调用的时候加载,并且只加载一次。
public class Singleton {
	// 内部类中实例化对象
    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }

	public static void main(String[] args) {
		Singleton instance = Singleton.getInstance();
	}
}

5.枚举

enum Singleton {
    INSTANCE;
}

public class Main {
	public static void main(String[] args) {
		// 获取实例对象
		Singleton instance = Singleton.INSTANCE;
	}
}
  • 避免了多线程同步问题。
  • 可以防止反序列化后重新创建新对象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值