设计模式之单例模式
单例模式确保某一个类只有一个实例,而且自行实例化并且向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。
单例的特点
- 某个类只能有一个实例
- 它必须自行创建这个实例
- 它必须自行向整个系统提供这个实例
- 单例模式是一种对象创建型模式
单例模式结构图
单例模式只包含一个Signgleton(单例角色),同时提供一个静态的getInstance()工厂方法,然后应用获取它的唯一实例;为了防止外部对其实例化,需要将构造方法设计为私有。
一般的单例模式实现
一般的单例模式,代码如下:
public class Singleton {
/*静态的变量声明*/
private static Singleton instance;
/*构造方法为private这样可以堵死外部使用new来创建对象的可能*/
private Singleton(){
}
/*静态工厂方法,是本类实例的唯一访问方法*/
public static Singleton getInstance(){
//如果实例不存在则创建一个
if (instance == null){
System.out.println("开始初始化...");
instance = new Singleton();
}
return instance;
}
}
多线程下的单例模式
关于上面的单例模式,在多线程下,是会存在问题的,会出现创建多个Singleton实例,下列是一个多线程调用的实例,100个线程并发调用Singleton的getInstance(),如果有问题则会出现调用多次初始化的方法:
public class MultiThreadTest {
public static void main(String[] args) {
//使用CountDownLatch来模拟100个线程同时并发
final CountDownLatch countDownLatch = new CountDownLatch(100);
for (int i= 0; i < 100; i++){
new Thread(new Runnable() {
@Override
public void run() {
//计数减去1
countDownLatch.countDown();
try {
//await等待到CountDownLatch的计数为0时再唤醒
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
Singleton.getInstance();
}
}).start();
}
}
}
运行后结果如下,每一次运行的结果都可能不一样,下面是跑了测试例子之后的结果,实例被创建了6次,显然上面的单例模式在多线程下是存在问题:
面对多线程的单例的问题,需要针对初始化的代码添加锁来实现,保证同一时刻只有一个线程进入初始化的代 码块,在一个线程初始化后,则其他线程再走到instance==null这个
条件是就不会进入分支了。Java中第一种方式,在方法上添加synchronized
关键字:
public class Singleton {
/*静态的变量声明*/
private static Singleton instance;
/*构造方法为private这样可以堵死外部使用new来创建对象的可能*/
private Singleton(){
}
/*静态工厂方法,是本类实例的唯一访问方法,在方法上添加synchronized关键字加锁*/
public synchronized static Singleton getInstance(){
//如果实例不存在则创建一个
if (instance == null){
System.out.println("开始初始化...");
instance = new Singleton();
}
return instance;
}
}
通过方法上添加synchronized
关键字加锁的方式,粒度过于大了,可以通过过代码块加锁的方式,缩小加锁的范围,并且通过双重检查,减小加锁的范围,在外层先通过if(instance == null)
先判断是否为null,如果不为null则不会加锁,这降低了锁的开销,较小性能开销,这种方式比较推荐的:
public class Singleton {
/*静态的变量声明*/
private static Singleton instance;
/*构造方法为private这样可以堵死外部使用new来创建对象的可能*/
private Singleton(){
}
/*静态工厂方法,是本类实例的唯一访问方法*/
public static Singleton getInstance(){
//如果实例不存在则创建一个
if (instance == null){
synchronized (Singleton.class){
//采取双重锁定的方式
if (instance == null){
System.out.println("开始初始化...");
instance = new Singleton();
}
}
}
return instance;
}
}
静态化初始化的单例模式
这种方式单例模式也被称为饿汉式的单例类,为什么这么说呢?因为它在类被加载进JVM时就将自己实例化了,哈哈,形象生动。前面的几种单例类实现都是懒汉式单例类,因为它们都是在需要时才真正实例化。下面是静态初始化的单例模式:
public class Singleton {
/*静态的变量声明,直接初始化了,懒汉式单例*/
private static Singleton instance = new Singleton();
/*构造方法为private这样可以堵死外部使用new来创建对象的可能*/
private Singleton(){
}
/*静态工厂方法,是本类实例的唯一方位方法*/
public static Singleton getInstance(){
return instance;
}
}
什么场景下使用单例模式
如果我们的类的初始化的开销非常大,且每一次初始化都是一样,也即是每次实例其实都是状态一致的实例,或者多个实例同时存在是会引发错误时,那么该类就应该设计成单例模式。典型的单例应用有:数据库的连接池的连接,Hibernate的SessionFactory实现。
单例模式优缺点
优点
- 提供了对唯一实例的受控访问
- 由于系统中只有一个对应的实例,故可以节省资源,减少创建对象和销毁的开销
缺点
- 由于单例中没有抽象层,故单例类的扩展比较困难
- 单例类的职责过重了,它既充当了工厂角色,提供了工厂方法
getInstance()
,又充当了产品角色,包含业务方法。不过这个工厂方法,是单例的访问入口,也是必须的,故也不算什么缺点,只能说是和某些设计模式的原则有冲突而已