单例模式

一、模式名

单例模式,Singleton

二、解决的问题

单例模式是设计模式中使用比较多并且形式比较简单的设计模式。单例模式用于提供一种方式,为某个类产生唯一实例。

单例模式主要应用在对象创建频繁并且对象创建或销毁资源消耗大的情况。为提高系统效率,一般会选用单例模式。

三、实现

实现单例分为两个步骤:1. 私有化类的构造器,限制用户无法通过new生成该类的对象;2. 为该类一般提供一个static方法,获取该类的单例对象。

单例的实现方式有很多:

1. 饿汉式

顾名思义,生成单例对象的时刻比较早。

class Singleton { 
    public static final Singleton singleton = new Singleton(); 
    private Singleton() {
    }
 
    public static Singleton getInstance() { 
        return singleton; 
    } 
}

饿汉式通过在类加载过程中就生成该类的对象,保证了实例唯一和线程安全。但是,由于类加载就产生了该类的对象,如果对象创建过程耗费很多资源,那么就会对类加载过程造成影响。

除了,通过在单例对象声明出创建对象,还可以在类加载的其他过程创建单例对象,比如在static代码块中。

2. 懒汉式

顾名思义,单例对象生成时刻比较晚,只有在客户端需要单例对象时才生成单例对象。

class Singleton { 
    public static Singleton singleton;
    
    private Singleton() {} 

    public static Singleton getInstance() {
         if (singleton == null) { 
            singleton = new Singleton();
         }
         return singleton;
     }
 }

可以看到,懒汉模式在需要单例对象时,才开始创建单例。但是上面的代码没有考虑到多线程的问题,可能出现两个线程调用getInstance()时,同时进入了singleton == null 的 if 判断,随后这两个线程都将生成新的单例对象,从而破环单例模式实例唯一。

有三种改进方式:a. 同步方法; b. 同步代码块; c. 双重校验

a. 同步方法

// 改写 getInstance 方法 
... 
public synchronized static Singleton getInstance() {
    if (singleton == null) { 
        singleton = new Singleton();
     } 
    return singleton;
}
 ...

同步方法通过为获取单例的方法加锁,使得获取单例方法同时只有一个线程执行,保证了线程安全,但是产生单例的代码只有几行,可能该方法中还有其他不受多线程影响的代码段,这些代码全都被锁住,可能会导致获取单例的方法执行效率比较低。

b. 同步代码块

// 改写 getInstance() 方法 
... 
public static Singleton getInstance() { 
    synchronized(Singleton.class) {
         if (singleton == null) { 
            singleton = new Singleton();
         }
         return singleton;
     }
 } 
... 

同步代码块只锁住生成实例的代码段,提高了效率。同时还可以进一步提高该方法的执行效率。

c. 双重校验

// 改写 getInstance() 方法 
... 
public static Singleton getInstance() {
    if (singleton == null) {
         synchronized(Singleton.class) { 
            if (singleton == null) { 
                singleton = new Singleton();
             }
         }
     }
     return singleton; 
}
 ...

通过使用双重校验,同步锁的粒度更小,获取实例的方法执行效率也会更高。

同时注意:为什么需要第二次单例的空值校验?因为可能会出现两个线程,都进入了第一层 if 判断,随后第一个线程获取了锁,进入了临界区,生成了单例,释放锁后,第二个线程也进入了临界区,如果此时没有第二层的 if 实例空值判断,则第二个线程也会生成一个新的实例对象,这样就破环了实例唯一的特点。所以,第二层 if 空值判断是保证线程安全的必须条件。

应用场景

1. 数据库连接对象

2. 工具类

...

posted @ 2019-06-19 00:07  锢浪熟阳  阅读(146)  评论(0编辑  收藏  举报