单例模式(Singleton Pattern)

单例模式(Singleton Pattern)

单例模式,从字面意思上来看,就是一个类只有一个实例。那为什么要有单例模式呢?带着这个问题往下看。

为什么要有单例模式(单例模式的应用场景)

1.需要生成唯一序列号

假设采用最简单的序列号生成方式,即连续的方式。设序列号id的初值为1,则每次生成序列号只需要将id++即可。在这种场景下,有多个实例(也就有了多个id)是没有必要的,尤其是在多线程的情况下,必须要保证id是单例的。

2.一个类的实例被频繁的调用和销毁

如果一个类被频繁的访问,例如大家熟知的DAO(数据访问对象)类,这个类的实例会被频繁的调用,如果不使用单例模式,在每次调用时,都要创建一个对象,调用完之后又被销毁;每次调用时,new一个对象都会消耗一定的时间,频繁的创建会带来更大的开销,所以,如果使用单例模式,每次都访问这一个对象,就免去了创建的开销。

3.整个项目的共享访问点或共享数据

例如,记录一个网页的访问量时,就可以将计数器设置为一个单例,每次访问页面时,都对计数器单例进行一次访问。

单例模式的定义

                                                     

确保一个类只有一个实例,而且自行实例化(在一个类中 ,使用new调用了自己的构造方法),并向整个系统提供这个实例

总结为三点:

  1. 整个系统只有一个实例。
  2. 这个实例由单例类自己创建(自行实例化)。
  3. 整个系统(其他所有对象),都可以访问这个实例。

※单例模式的实现

实现的核心思想:将构造方法私有化(防止生成多个实例),自行实例化。

在介绍实现方式之前,先说一下延迟初始化,也就是在使用时才初始化。

1.线程不安全的懒汉式

是否为延迟初始化线程是否安全
public class Singleton{

    private static Singleton singleton = null;

    private Singleton(){
    }

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

可以看到,在多线程的环境下,可能会创建多个实例,所以,可以将它进行改造成下面要说的线程安全的懒汉式。

2.线程安全的懒汉式

是否为延迟初始化线程是否安全
public class Singleton{

    private static Singleton singleton = null;

    private Singleton(){
    }

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

对这个方法加上互斥锁,就保证了只会创建一个实例,但是新的问题又来了,如果唯一的实例已经被创建,这个方法的互斥锁就是没有必要的,有了互斥锁,反而会降低效率,所以,再次改造,称为下面的双检索。

3. 双检索(DCL, Double-checked Locking)

是否为延迟初始化线程是否安全
public class Singleton{

    private static volatile Singleton singleton = null;

    private Singleton(){
    }

    public static Singleton getInstance(){
        if(singleton == null){

            synchronized (Singleton.class){
                if(singleton == null)
                    singleton = new Singleton();
            }

        }
        return singleton;
    }
}

如果有多个线程进入了第一个if,则仅有一个线程可以创建实例,其他线程被阻塞,直到实例创建完成,其他实例才能进入synchronized代码块,此时实例已经创建完成,则在第二次if判断时,不会创建实例。除了创建实例的时候,有可能会有多个进程进入synchronized代码块,造成较小的阻塞开销;与线程安全的懒汉式相比,实例创建好之后,双检索的效率更高。

使用volatile修饰的原因,一个线程调用构造方法初始化实例时,指令有可能被重排序导致出错(由于对JVM了解的不多,所以以后进行补充),使用volatile修饰之后,禁止编译器对指令进行重排序。

4.饿汉式

是否为延迟初始化线程是否安全
public class Singleton{

    private static Singleton singleton = new Singleton();

    private Singleton(){
    }

    public static Singleton getInstance(){
        return singleton;
    }
}

采用饿汉式的方式,是应用了类在加载时进行初始化,这样就不能延迟初始化(不是在使用时初始化),造成了一定的资源浪费。但是这种方式足够简单。为了解决不能延迟初始化的问题,可以使用下面的静态内部类的方式解决。

5.登记式/静态内部类

是否为延迟初始化线程是否安全
public class Singleton{

    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }

    private static Singleton singleton = null;

    private Singleton(){
    }

    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

在Singleton被加载时,SingletonHolder没有被加载,因为将SingletonHolder声明为private,所以,只有在Singleton类的内部才可以访问,只有在访问这个类时,这个内部类才会被加载;也就是说,只有在第一次调用getInstance()时,这个内部类被加载,完成初始化操作。

6.枚举

是否为延迟初始化线程是否安全
public enum Singleton {  
    INSTANCE;  
    public void serverOfSingleton() {  
    }  
}

枚举是天然的单例,这是实现单例最简单的方式。

单例模式的优势

  1. 在内存中仅存在一个实例,在频繁的创建和销毁时,会提高效率。
  2. 避免资源的多重占用,例如,避免一个文件被多个线程的不同实例同时写入。
  3. 给系统设置全局访问点。(这是单例模式的定义中的一个特点)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值