设计模式一:单例模式

单例模式定义

确保类只有一个实例,而且自行实例化并向整个系统提供这个实例

通用代码

// 代码1 饿汉式单例
public class Singleton {
    private static final Singleton singleton = new Singleton();
    // 限制产生多个对象
    private Singleton() {
    }
    // 通过这个方法获取实例对象
    public static Singleton getSingleton() {
    }
    // 类中其他方法,尽量是static
    public static void doSomething() {
    }
}

注意事项

高并发时注意单例模式的线程同步问题,使用上面的通用代码不会产生线程同步问题。但是采用下面的代码就需要考虑线程同步

// 线程不安全的单例模式 代码2
public class Singleton {
    private static Singleton singleton = null;
    private Singleton() {}
    public static Singleton getSingleton() {
        if (singleton == null) singleton = new Singleton();
        return singleton;
    }
}
  • 当线程A执行到singleton = new Singleton(),但还没有获得对象,对象初始化需要时间,线程B也执行到singleton == null,那么线程B获得判断条件为真。导致线程A,B分别获得一个对象,内存中存在两个对象。
  • 解决办法,在getSingleton方法前加synchronized关键字,称为懒汉式单例

单例模式优点

  • 减少内存使用
  • 减少了系统的性能开销
  • 避免对资源的多重占用

单例模式缺点

  • 一般没有接口,扩展困难
  • 对测试不利。在并行开发环境中,如果单例模式没有完成,不能进行测试,没有接口也不能使用mock方法虚拟一个对象。
  • 单例模式与单一职责原则有冲突。一个类应该只实现一个逻辑,而不关心它是否是单例的,是不是要单例取决于环境,单例模式把“要单例”和业务逻辑融合在一个类中

使用场景

  • 要求生成唯一序列号的环境
  • 需要一个共享访问点,共享数据
  • 创建对象需要消耗的资源过多,如访问IO和数据库等
  • 需要定义大量的静态常量和静态方法(如工具类)的环境

单例模式的扩展

有上限的多例模式:要求一个类只产生两个对象

// 考虑到线程安全可用Vector代替ArrayList
public class Emperor {
    // 定义最多产生几个实例数量
    private static int maxNumOfEmperor = 2;
    // 每个皇帝都有名字,使用ArrayList来容纳,每个对象的私有属性
    private static ArrayList<String> nameList = new ArrayList<String>();
    // 定义一个列表,容纳所有的实例对象
    private static ArrayList<Emperor> emperorList = new ArrayList<Emperor>();
    // 当前皇帝的序号
    private static int countNumOfmperor = 0;
    // 产生所有的对象
    static {
        for (int i = 0; i < maxNumOfEmperor; i++) {
            emperorList.add(new Emperor("皇"+(i + 1)+"帝"));
        }
    }
    // 不允许外部代码实例化该类
    private Emperor() {}
    // 传入皇帝名字,建立一个皇帝对象
    private Emperor(String name) { 
        nameList.add(name);
    }
    // 随机返回一个皇帝对象
    public static Emperor getInstance() {
        Random random = new Random();
        countNumOfmperor = random.nextInt(maxNumOfEmperor);
        return emperorList.get(countNumOfmperor);
    }
    // 皇帝发话
    public static void say() {
        System.out.println(nameList.get(countNumOfmperor));
    }
}

最佳实践

  • 使用单例模式需要注意JVM的垃圾回收机制,如果一个单例对象在内存长时间不用,JVM会认为这是一个垃圾而回收,在下次知道用时需要重新产生一个对象。如果单例模式的类是有状态值的,如计数器,那么会出现恢复原状的情况。解决办法如下
    1. 使用容器管理单例的生命周期
      Java EE容器或者框架级容器(Spring等)可以让对象长久驻留内存
    2. 状态随时记录
      可以使用异步记录的方式,或者观察者模式,确保即使单例对象重新初始化也能从资源环境获得销毁前的数据。

参考

《设计模式之禅》

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值