文章目录
0. ref
- 最好的设计模式 总结 博客
https://www.cnblogs.com/maowang1991/archive/2013/04/15/3023236.html
- hensen 设计模式速攻
https://blog.youkuaiyun.com/qq_30379689/article/details/77349786
1. lazy 线程不安全的
public class Singleton {
Singleton s;
private Singleton(){}; // rem
public static Singleton getInstance () {
if (s == null) {
s = new Singleton();
}
return s; // rem
}
}
2. lazy 线程安全的
public class Singleton {
Singleton s;
private Singleton(){}; // rem
public static Singleton getInstance () {
synchronize(Singleton.class) {
if (s == null) {
s = new Singleton();
}
return s; // 不要再这里返回吧
}
}
}
改正
public class Singleton {
Singleton s;
private Singleton(){}; // rem
public static Singleton getInstance () {
synchronize(Singleton.class) {
if (s == null) {
s = new Singleton();
}
}
return s; // rem 在这里返回
}
}
fix: 前面两个都没有加上的private static 前缀
应该让所有类实例都持有同一个单例对象(尽管我们的类实例的构造器是private的,但是也要遵循封闭式原则,如下:
Singleton s;
改为:
private static Singleton s = null;
这样的话,第二个多线程安全的单例就是:
public class Singleton {
private static Singleton s = null;
private Singleton(){}; // rem
public static Singleton getInstance () {
synchronize(Singleton.class) {
if (s == null) {
s = new Singleton();
}
}
return s; // 推荐 在这里返回
}
}
3. lazy 双重校验锁
第二个方式的加锁太重量级了,也就是说,锁太大了,只要访问了getInstance()
这个方法,就会进行加锁,就是会发生创建代码中的临界区,这个加锁的动作,会消耗一定的cpu指令周期,以至于程序整体的效率会因此(经常加锁)而降低。
于是,方案3来了: 就是在单例不存在的时候,才去新建,而且新建单例的时候,保持多线程安全。
代码如下:
public class Singleton {
private static Singleton s = null;
private Singleton(){};
public static Singleton getInstance () {
if (s == null) { // here 划重点!单例不存在的时候
synchronize(Singleton.class) {// 而且新建单例的时候,加锁保持多线程安全
if (s == null) {
s = new Singleton();//才去新建
}
}
}
return s; // 推荐 在这里返回
}
}
这个方式3 照他说的,
还是有点危险
在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton(); 这个语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间,然后(1)直接赋值给instance成员,然后再(2)去初始化这个Singleton实例,但是万一一个其他的线程B 在2还没做的时候进来,(eg 1.5)的时候,这样就可能出错了
我们以A、B两个线程为例:
-
A、B线程同时进入了第一个if判断
-
A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();
-
由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。
-
B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
-
此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。
4. eager, 加了volatile 的lazy 懒汉?
非也,在一开始声明的时候就初始化, 这是有多饿
public class Singleton {
//在一开始声明的时候就初始化
private volatile static Singleton s = new Singleton();
private Singleton(){};
public static Singleton getInstance() {
return s;
}
}
5. 静态内部类,用了JVM内部的机制
用了 jvm 的特性:
JVM内部的机制能够保证: 当一个类被加载的时候,这个类的加载过程是线程互斥的
class OutterSingleton {
private OutterSingleton() {}
private static class SingletonFactory{
private static OutterSingleton s = new OutterSingleton();
}
public static OutterSingleton getInstance() {
return SingletonFactory.s;// 你可以访问内部类的private
}
}
public class singletonTestInnerPrivate{
public static void main(String[] args) {
OutterSingleton m = OutterSingleton.getInstance();
System.out.println("done");
}
}
6. 枚举
// version 1.6
public enum EnumSingleton {
INSTANCE;
public void doSomeThing() {
}
}
这是利用了1.6 的新特性,请todo