Java, C++ 单例模式与静态成员初始化对比

Java

Java里有一个比较晦涩的地方:
final 静态域在初始化前,存在着读取其值的可能性。”
“final 类型的区域只有在其初始化表达式是 常量表达式 的时候才是常量”


这一点结合上初始化训话就会导致很奇怪的事情。

这里来一个《Java解惑》的例子:

package lusters;

import java.util.Calendar;

public class Base {
    //    private static final int staticVar = Calendar.getInstance().get(Calendar.YEAR);

    public static final Base single = new Base();
    static final int staticVar = Calendar.getInstance().get(Calendar.YEAR);


    private final int value;

    private Base() {
        value = staticVar - 1000;
    }

    public int size() {
        return this.value;
    }

    public static void main(String[] argvs) {
        System.out.println(single.size());
        System.out.println(single.staticVar);
    }
}

输出是

-1000
2015

问题在于,println 语句导致Base初始化,在初始化静态域的时候,先设为默认值,然后调用Base的构造函数初始化。此时,staticVar=0(默认),所以single.value = -1000。之后staticVar才被初始化。

书上给出的建议是 “要想改正一个类初始化循环,需要重新对静态域的初始器进行排序,使得每一个初始器出现在任何一个依赖它的初始器之前。”

把 staticVar 的初始化移到 single上面就可以了。


对于单例这种设计模式来说,常用的有这几种:饿汉,懒汉,嵌套类(再见实在是想吐槽这个饿汉懒汉)


饿汉式

饿汉式会出现初始化依赖的情况

public class Singleton {

    //1
    //Yep ;)

    public static final Singleton single = new Singleton();

    //2
    //Hell No!!!此处的静态变量均有可能发生问题

    private Singleton() {
        /*
        Do Sth Relay on Static Var
        */
    }
}



懒汉式

相比较之下懒汉式没有初始化依赖但是需要注意的是线程安全

volatile 声明单例空引用,之后在getter方法中双重检查引用,同时对 class synchronized加锁 

public final class Singleton {

    //volatile 防止无序写入
    private static volatile Singleton single = null;

    public static Singleton getSingle() {
        //外层检查减少锁开销
        if(single == null)
            synchronized (Singleton.class) {
                if (single == null)
                    single = new Singleton();
            }
        return single;
    }

    private Singleton() {/*...*/}
}


嵌套类

public final class Singleton {

    private static class SingleHolder {
        private static final Singleton single = new Singleton();
    }

    public static Singleton getSingle() {return SingleHolder.single;}

    private Singleton() {}
}

嵌套类是三者中比较好的一种
1、类似饿汉式保证了线程安全
2、虽然存在初始化依赖,但使用内部类避免了静态final域初始化顺序的问题(内部静态single调用Base()构造器之前,外部静态类初始化完毕)

对比

 
饿汉式
懒汉式静态嵌套类

静态域初始化问题
存在,需要仔细调整静态域顺序不存在不存在

线程安全
安全,类加载器保证不安全,需要自己保证安全,类加载器保证
代码可读性高,直观低,代码量大
高,直观

C++

C++相对于Java来说,没有类加载机制,因此不会出现初始化循环:全局静态变量与静态成员或者是在编译时期就已确定(const)或者是在程序一开始的时候初始化。
(P.S. 个人感觉C++的静态成员变量与类的关系,也并没有Java那样联系那么紧密)
(又一个 P.S. 但是C++的全局静态变量初始化顺序很蛋疼啊 ……)

C++比较常用的依然有三种:饿汉式,懒汉式(内部静态),懒汉式(静态成员)(再次吐槽 再见

这里先介绍饿汉式

饿汉式

class Singleton final {
    
    //禁用拷贝赋值函数
    Singleton(const Singleton&) =delete;
    Singleton operator==(const Singleton&) =delete;
    
    static Singleton* single_ptr;
    
    //私有构造函数
    //1、禁止继承行为
    //2、防止外界构造对象
    Singleton() {}
    
    //私有析构函数
    //防止外界显式调用析构函数
    ~Singleton(){}
    
public:
    
    static Singleton* single() {
        return single_ptr;
    }
    
private:
    //静态成员
    //由于析构函数私有,导致单例无法析构,
    //使用私有内部类创造一个删除器,
    //程序退出时,析构静态成员同时析构单例,防止内存泄漏
    class Deleter {
    public:
        ~Deleter(){
            delete single_ptr;
        }
    };
    static Deleter deleter;
};

Singleton* Singleton::single_ptr = new Singleton();
Singleton::Deleter Singleton::deleter = Singleton::Deleter();
类似的原因,静态成员在开始的时候初始化,保证了线程安全

懒汉式(内部静态)

懒汉式(内部静态)就简单很多,利用的是静态变量只初始化一次。
class Singleton final {
    
    //禁用拷贝赋值函数
    Singleton(const Singleton&) =delete;
    Singleton operator==(const Singleton&) =delete;
    
    //私有构造函数
    //1、禁止继承行为
    //2、防止外界构造对象
    Singleton() {}
    
    //私有析构函数
    //防止外界显式调用析构函数
    ~Singleton(){}
    
public:
    
    static Singleton* single() {
        
        //C++11之后,编译器保证了静态变量初始化线程安全
        //但是C++11之前依然需要加锁
        //lock.lock();
        
            //静态变量,只初始化一次
            static Singleton single;
        
        //lock.unlock();
        
        
        return &single;
    }
};
但是,在C++11之前,并不能保证初始化时线程安全,就意味着的锁不能去掉,每一次访问都会有锁的开销。


懒汉式(静态成员)

懒汉式的改变也很小,只需要把单例指针初始化为空,之后构造时加锁就可以了

class Singleton final {
    
    //禁用拷贝赋值函数
    Singleton(const Singleton&) =delete;
    Singleton operator==(const Singleton&) =delete;
    
    static Singleton* single_ptr;
    
    //私有构造函数
    //1、禁止继承行为
    //2、防止外界构造对象
    Singleton() {}
    
    //私有析构函数
    //防止外界显式调用析构函数
    ~Singleton(){}
    
public:
    
    static Singleton* single() {
        
        //外层检查减少锁的使用
        if (single_ptr == nullptr) {
            
            //这里使用的是C++11的 std::mutex
            //也可以使用其他锁,比如自旋锁
            lock.lock();
            
            if (single_ptr == nullptr) single_ptr = new Singleton();
            
            lock.unlock();
        }
        return single_ptr;
    }
    
private:
    //静态成员
    //由于析构函数私有,导致单例无法析构,
    //使用私有内部类创造一个删除器,
    //程序退出时,析构静态成员同时析构单例
    class Deleter {
    public:
        ~Deleter(){
            delete single_ptr;
        }
    };
    static Deleter deleter;
};

Singleton* Singleton::single_ptr = nullptr;
Singleton::Deleter Singleton::deleter = Singleton::Deleter();


对比
 
饿汉式

懒汉式(内部静态)

懒汉式(静态成员)

锁开销



C++11之前很高,C++11之后无

低(一次)
代码可读性
较低



P.S.
其实对于锁还有优化的方法

锁带来的开销有两个方面

1、每次都需要判断指针
2、锁本身开销

pthread_once() 可以一次解决这个问题,POSIX提供的一次初始化函数保证代码块只执行一次,而且速度比if快(OS X 和 BSD 上,有封装的API: dispatch_once())




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值