Java设计模式-单例模式

背景

武器库(WeaponStore)是存放武器的地方,所有机体都在武器库拿武器,那么武器库就是单例的。

常规实现

武器库:存放武器,设置为单例

/**
 * 武器库
 */
public class WeaponStore {
    
    /**
     * 武器:Map<武器名,数量>
     */
    private Map<String, Integer> weapons = new HashMap<>();

    private static WeaponStore weaponStore;

    /**
     * 单例模式不能够实例化
     */
    private WeaponStore() {
        super();
        weapons.put("狮子王剑", 1);
        weapons.put("重力炮", 2);
        weapons.put("等离子切割刀", 10);
        weapons.put("马加爵之锤", 4);
    }
    
    public static WeaponStore getInstance() {
        if (weaponStore == null) {
            weaponStore = new WeaponStore();
        }
        return weaponStore;
    }
    
    /**
     * 拿武器
     */
    public void getWeapon(String weaponName) {
        Integer count = weapons.get(weaponName);
        if (null != count && count.intValue() > 0) {
            count--;
            weapons.put(weaponName, count);
            System.out.println("装备" + weaponName);
        } else {
            System.out.println(weaponName + "库存不足");
        }
    }
    
}

测试类:测试100个机体同时在武器库拿武器的情况

public class WeaponStoreTest {

    public static void main(String[] args) {
        
        for (int i=0; i<100; i++) {
            // 现在有100个机体同时在仓库拿狮子王剑,会出现初始化多个单例对象的bug
            Thread ancientIronThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    WeaponStore.getInstance().getWeapon("狮子王剑");
                }
            });
            ancientIronThread.start();
        }
    }
    
}

执行结果

装备狮子王剑
狮子王剑库存不足
装备狮子王剑
狮子王剑库存不足
狮子王剑库存不足
狮子王剑库存不足
狮子王剑库存不足
狮子王剑库存不足
...
分析

武器库原本是单例的,但在多线程情况下创建了多个武器库,不符合预期。
有一种解决方法是在getInstance方法上加synchronize修饰符,不过不推荐使用,会影响效率。
另一种解决方法是使用双重检查锁

使用双重检查锁改进武器库(部分代码)
private static volatile WeaponStore weaponStore;

public static WeaponStore getInstance() {
	// 双重检查锁:两次检查是否为空实例
	if (weaponStore == null) {
		synchronized (WeaponStore.class) {
			if (weaponStore == null) {
				weaponStore = new WeaponStore();
			}
		}
	}
	return weaponStore;
}
  • 双重检查锁
    在获取实例的时候,判断实例是否存在,若不存在,进入阻塞代码,继续判断实例是否存在,如果不存在,就实例化对象。
    为什么双重检查锁需要volatile关键字?
    因为weaponStore = new WeaponStore不是原子操作,可简单分为三个操作:
  1. 分配内存memory = allocate()
  2. 初始化对象initInstance(memory)
  3. 指向分配的内存空间weaponStore = memory

由于Java有指令重排的特点,上面三个操作可能会以132的顺序执行,那么如果一个线程执行3操作后,另一个线程去获取实例,那么获取到的实例就是半初始化的。使用volatile是因为volatile有禁止指令重排的特点。

总结

上面的实现方式叫:懒汉式单例模式。
懒汉式单例模式:就是在类加载的时候不实例化对象,而是调用的时候再实例化(懒到什么程度呢?用到的时候再初始化。类比懒加载或许更好理解),这种方式会有线程安全问题,解决方案是使用双重检查锁。
另一种实现方式叫:饿汉式单例模式
饿汉式单例模式:就是在类加载的时候实例化对象,调用的时候直接返回(饿到什么程度呢?不管有没有用到,先初始化再说),这种方式没有线程安全问题,但是每次类加载的时候都会实例化对象,会造成不必要的性能开销。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值