1 定义
单例模式确保一个类仅有一个实例,并且自行初始化且向整个系统提供使用。
2 使用场景
- 某个类要求只生成一个对象的场景,如一个国家只有一个皇帝。
- 整个项目需要一个共享访问点,如网站的上的登陆人数计数器。
- 某一个类的对象需要频繁的被创建与销毁,并且创建销毁性能无法被优化,如线程池,网络连接池。
- 一些硬件级别的操作,需要一个单一的操作入口。
- 对象需要被共享的场合,如配置对象、数据库连接池。
3 优缺点
优点:
- 保证内存里只有一个实例,减少内存开销。
- 避免资源被多重占用。
- 设置全局访问点,优化共享资源的访问。
缺点: - 违背开闭原则:单例模式通常没有接口很难扩展,一般只有修改原有的类。
- 违背单一职责原则:单例模式的功能通常都写在一个类中
- 测试困难:在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象。
4 实现
饿汉式
饿汉式表明在类被加载的时候就建立单例对象。
public class HungrySingleton {
private static final HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){};
public static HungrySingleton getHungrySingleton(){
return hungrySingleton;
}
}
懒汉式
只有在第一次获取单例对象时采取创建单例对象,优点:节省内存开销,加快程序启动速度。
实现方式1-synchronized
public class LazySingleton {
private static volatile LazySingleton lazySingleton;
private LazySingleton(){}
public static synchronized LazySingleton getLazySingleton(){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
其中synchronized与volatile关键字是为了保证线程安全,缺点是每次访问都需要加锁,并发性差,并且消耗更多资源。
实现方式2-双重检查
实现方式1是在方法上加锁,每次get的时候都会执行加锁动作,即使实例已经被初始化完成。那么能否在判断实例未初始化时再加锁进行创建呢,这样get实例的时候就不用加锁了。实现方式如下:
public class LazySingletonDoubleCheck {
private static volatile LazySingletonDoubleCheck instance;
private LazySingletonDoubleCheck(){};
public LazySingletonDoubleCheck getInstance(){
if(instance == null){
synchronized(LazySingletonDoubleCheck.class){
if(instance == null){
instance = new LazySingletonDoubleCheck();
}
}
}
return instance;
}
}
其中第一次检查是看实例是否被初始化,没有的话则对类进行加锁,获得锁后会再次判断实例是否被初始化(防止多个线程同时加锁的情况),如果实例未被初始化,则进行初始化。