单例模式

一、是什么

1.1、单例类只能有一个实例

1.2、单例类必须自己创建自己的唯一实例

1.3、单例类必须给所有其他对象提供这一实例(必须有一个public的获取实例的方法)

 

二、适用环境

单例模式可以保证全局对象的唯一性,比如系统启动读取配置文件就需要单例保证所有配置的一致性。一般以下情况常考虑单例模式的设计模式:

2.1、系统只需要一个实例对象,或者因为资源消耗太大只允许创建一个实例对象。

2.2、客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例,说白了就是访问入口只有一个的情况。

 

三、细化单例模式&代码

3.1、饿汉式单例

当类Singleton1被加载时,类的静态变量instance会被初始化,此时类的构造函数会被调用,单例类的唯一实例将被创建。也就是说饿汉式单例在类加载时便会创建唯一实例

优点:无需考虑多线程问题;可以确保实例唯一性。调用速度快,反映时间短。

缺点:资源利用效率低,占用率高。

public class Singleton1 {

    private static Singleton1 instance = new Singleton1();
    
    private Singleton1(){}
    
    public static Singleton1 getInstance(){
        return instance;
    }
}

3.2、懒汉式单例

当类Singleton2被加载时,不会创建类的实例,在需要实例的的时候再创建加载实例,这种技术又叫延迟加载(Lazy Load)。

通过关键字synchronized解决线程安全问题。

优点:资源利用率高。

缺点:线程安全控制繁琐;系统性能会受影响。

public class Singleton2 {

    private static Singleton2 instance = null;  //静态私有成员变量
    
    //私有构造函数
    private Singleton2(){}
    
    //为避免多个线程同时调用getInstance()方法,使用关键字synchronized对方法加锁,
    //确保任意时刻只有一个线程可执行该方法
    synchronized public static Singleton2 getInstacne(){
        if (instance == null) {
            instance = new Singleton2();
        }
        return instance;
    }
}

上述代码可以解决线程安全的问题,但是每次调用getInstance()方法时,都要进行线程锁定判断,在多线程高并发环境下会导致系统性能降低,为解决此类问题问题,应将加锁范围缩小到创建实例时。故做如下改进:

public class Singleton2 {

    private static Singleton2 instance = null;  //静态私有成员变量
    
    //私有构造函数
    private Singleton2(){}
    
    public static Singleton2 getInstance(){
        if (instance == null) {
            synchronized (Singleton2.class) {
                instance = new Singleton2();
            }
        }
        return instance;
    }
}

3.3、双重检查锁定

上述3.2 懒汉式单例类仍然存在单例对象不唯一的情况,例如某一瞬间,线程A和线程B同时调用getInstance()方法,在synchronized加锁机制下,A线程率先执行加锁代码块创建实例,线程B处于等待状态。当A执行完毕时,线程B并不知道实例已被创建,将会继续创建一份新的实例,这样就产生了多个实例对象。

注意:使用双重检查锁定实现懒汉式单例类时,需要在静态成员变量instance前增加修饰符volatile,被volatile修饰的成员变量可以确保多个线程都能正确处理,需要JDK1.5以上(话说现在也不用再低的版本了吧...)。

这里解释一下为什么用volatile关键字:

如果不使用volatile关键字,在第二重判断instance=null后,执行instance = new instance3()时,并不是一个原子操作,他会分很多步:1.给instance分配内存;2.调用Singleton构造完成初始化;3.使instance对象的引用指向分配的内存空间(完成这一步instance就不是null了)。但是 JVM 虚拟机会做指令重排序的优化.也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2.如果是1-3-2的话,在 3 执行完毕、2 仍未执行之前,该线程CPU执行权被其他线程B抢占了,这时 instance 已经不是 null 了(但是还未初始化),所以线程B会直接返回 instance,然后使用,然后会报错。使用volatile关键字后,volatile关键字会屏蔽Java虚拟机做的一些代码优化,例如指令重排序优化,所以可以避免此类问题。

volatile关键字的特点与作用:

1、保证此变量对所有的线程的可见性,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存来完成。

2、禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障。

3、volatile 的读操作性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。

关于类加载机制,可以看这篇博客:Java类加载机制、初始化顺序

优点:解决懒汉式单例类存在单例不唯一的问题。

缺点:volatile关键字会屏蔽Java虚拟机做的指令重排序优化,可能会导致系统的运行效率降低。

故将继续改进如下:

public class Singleton3 {

    private volatile static Singleton3 instance = null;
    
    private Singleton3(){}
    
    public static Singleton3 getInstance(){
        //第一重判断
        if (instance == null) {
            //锁定代码块
            synchronized (Singleton3.class) {
                //第二重判断
                if (instance == null) {
                    instance = new Singleton3();
                }
            }
        }
        return instance;
    }
}

3.4、静态内部类实现单例模式

饿汉式单例类不能实现延迟加载,不管将来用不用单例类,始终都会占据内存;

懒汉式单例类(synchronized上锁、双重检查锁定)线程安全控制繁琐,且性能受到影响;

还可使用静态内部类实现单例模式,既可以实现延迟加载,又可以保证线程安全,不影响系统性能。因为在Java中,静态内部类和非静态内部类一样,都不会因为外部类的加载而加载,同时静态内部类的加载不需要依附外部类,在使用时才加载,不过在加载静态内部类的过程中也会加载外部类。

 

public class Singleton4 {

    private Singleton4(){}
    
    private final static class innerClass{
        private static Singleton4 instance = new Singleton4();
    }
    
    public static Singleton4 getInstance(){
        return innerClass.instance;
    }
    
    /*//测试代码段
    public static void main(String[] args){
        Singleton4 s1,s2;
        s1 = Singleton4.getInstance();
        s2 = Singleton4.getInstance();
        System.err.println("s1 = s2? 答案是 " + (s1==s2)); //s1 = s2? 答案是 true
    }*/
}

 

以上代码实现部分源码参照《Java设计模式》-(刘伟)一书。

总结如有错误、不足与改进之处,希望大家指正,不胜感激!

 

以上!

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值