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++比较常用的依然有三种:饿汉式,懒汉式(内部静态),懒汉式(静态成员)(再次吐槽
)
![再见](http://static.blog.csdn.net/xheditor/xheditor_emot/default/bye.gif)
这里先介绍饿汉式
饿汉式
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())