一、简介
单例模式,指的是某一个类,只允许实例出一个对象存在。而实现单例模式有懒汉式和饿汉式。饿汉式指的是在创建类时就初始化好对象,,而懒汉式指的是在需要使用到对象实例时,才进行初始化对象。
二、实现方式
饿汉式:
/**
* 饿汉式单例模式
*/
public class HungerSingleton {
private static HungerSingleton instance=new HungerSingleton();
private HungerSingleton(){}
public static HungerSingleton getInstance(){
return instance;
}
}
懒汉式(不考虑线程安全):
/**
* 懒汉式单例
*/
public class LazySingleton {
private static LazySingleton instance=null;
private LazySingleton(){
System.out.println("初始化LazySingleton..........");
}
public static LazySingleton getInstance(){
if(instance==null)
instance=new LazySingleton();
return instance;
}
}
三、懒汉式的线程不安全
懒汉式创建单例,在需要时才创建对象,因此在空间上更加友好。但也存在多线程的问题,比如线程1调用getInstance执行到
instance=new LazySingleton();
线程2也调用getInstance,并且判断出instance==null,也同样执行
instance=new LazySingleton();
这就导致了多线程下可能创建多个对象,这就不符合我们的预期了,测试代码如下:
public class Singleton {
public static void main(String[] args) {
for(int i=0;i<20;i++){
new Thread(new Runnable() {
public void run() {
LazySingleton.getInstance();
}
}).start();
}
}
}
四、DCL双端检测锁
使用双端检测锁,解决这种问题,代码如下:
/**
* 懒汉式单例
*/
public class LazySingletonNew {
private static LazySingletonNew instance=null;
private LazySingletonNew(){
System.out.println("初始化LazySingletonNew..........");
}
public static LazySingletonNew getInstance(){
if(instance==null){
synchronized (LazySingletonNew.class){
if(instance==null)
instance=new LazySingletonNew();
}
}
return instance;
}
}
测试代码如下:
public class Singleton {
public static void main(String[] args) {
for(int i=0;i<20;i++){
new Thread(new Runnable() {
public void run() {
LazySingleton.getInstance();
}
}).start();
}
for(int i=0;i<20;i++){
new Thread(new Runnable() {
public void run() {
LazySingletonNew.getInstance();
}
}).start();
}
}
}
五、使用volatile+DCL
使用上述的DCL,似乎已经可以解决线程安全问题,而实际上上面的DCL仍然有出现问题的可能性,虽然说很小,分析如下:
instance=new LazySingletonNew();并非是原子操作,new创建对象分为三步:
1、内存中分配一段空间
2、初始化该空间
3、将instance指向该空间
而由于指令可能存在重排(编译器优化或者cpu优化),可能执行的顺序为1、 3、 2、
那么,可能存在如下:
线程1创建空间,在synchronize代码块中,new对象的指令顺序为1、 3、 2,并且未执行到3时由于线程调度切换为线程2,线程2再次判断if(instance==null),发现不为null,因此将未初始化的空间拿来用了。这时,调用一个未初始化的空间里面的方法,变量名等将可能导致错误。
因此,使用volatile禁止指令重排。
代码如下:
/**
* 懒汉式单例
*/
public class LazySingletonNew {
private static volatile LazySingletonNew instance=null;
private LazySingletonNew(){
System.out.println("初始化LazySingletonNew..........");
}
public static LazySingletonNew getInstance(){
if(instance==null){
synchronized (LazySingletonNew.class){
if(instance==null)
instance=new LazySingletonNew();
}
}
return instance;
}
}