单例模式:
饿汉式:
public class MyObject {
private static MyObject obj = new MyObject();
private MyObject(){}
public static MyObject getInstance(){
return obj;
}
}
懒汉式:
public class MyObject {
private static MyObject obj;
private MyObject(){}
public static MyObject getInstance(){
if(obj == null){
obj = new MyObject();
}
return obj;
}
}
懒汉式的代码如果写成上面这样,在多线程环境中是错误的,是不能实现单例的。
解决:
1)、 对getInstance()声明synchronized
2)、 在getInstance()方法中使用同步代码块
3)、 使用DCL双检查锁机制
分析 1、2性质相同,效率低
DCL双检查锁分析:
public class MyObject {
private static MyObject obj;
private MyObject(){}
public static MyObject getInstance(){
if(obj == null){
synchronized(MyObject.class){
if(obj == null){
obj = new MyObject();
}
}
}
return obj;
}
}
描述:
除第一次创建对象外,其他的线程在访问到第一个if时,就返回了,不会进入同步代码块,效率提高了。
DCL存在的问题:
由于obj = new MyObject();并非原子性操作[即该语句要么执行完成,要么没有执行过],而JVM在执行该语句时,是分了三步执行的:
1)、为obj实例分配内存
2)、初始化MyObject的构造器
3)、将MyObject对象指向为obj分配的内存空间,【注:此时obj已经非null】
JVM编译器是允许处理器乱序执行的,所以执行顺序可能是 1-2-3或1-3-2。如果是1-3-2,在步骤3执行完成而步骤2还未执行的时候,被切换到了线程B上,由于线程A已经执行完了步骤3,所以obj是非null。导致线程B不再去实例化obj,直接拿着obj去使用,导致报错。(因为obj未完成初始化)
那么如何避免这样的问题呢?
可以使用volatile关键字,将变量声明为:
private volatile static MyObject obj = null;
最后,介绍一个相对完美的解决方案---使用静态内部类实现单例模式
public class MyObject {
private MyObject(){}
private static class MyObjectHandler{
private static MyObject obj = new MyObject();
}
public static MyObject getInstance(){
return MyObjectHandler.obj;
}
}
创建对象的过程交给了JVM,因此可以保证线程的安全;
MyObjectHandler是私有的,只有通过getInstance()访问,进而保证了延迟加载特性
获取实例时,也避免了使用同步,进而提升了性能