一、饿汉模式(在类加载时单例初始化完成,保证getInstance的时候,单例是已经存在)
public class Singleton{
private static final Singleton SINGLETON=new Singleton();
private Singleton(){};
public static Singleton getInstance(){
return SINGLETON;
}
}
二、懒汉模式(只有当调用getInstance的时候,才初始化单例)
public class Singleton1{
private static Singleton1 singleton1=null;
private Singleton1(){};
public static Singleton1 getInstance(){
if(singleton1==null){
singleton1=new Singleton1();
}
return singleton1;
}
}
当在多线程的情况下,可能会由线程异步导致创建多个实例的情况,改进的办法
方法一:给方法加锁
class Singleton2{
private static Singleton2 singleton2=null;
private Singleton2(){};
public static synchronized Singleton2 getInstance(){
if(singleton2==null){
singleton2=new Singleton2();
}
return singleton2;
}
}
方法二:双重检验
其实,当Singleton的变量为空的时候,我们才进行实例化,所以我们可以把方法一优化为锁的方法块,减少同步时间
同时我们还要避免无序写入
为解释该问题,考察下面代码块清单中的 //3 行。此行代码创建了一个 Singleton 对象并初始化变量 instance 来引用此对象。这行代码的问题是:在 Singleton 构造函数体执行之前,变量 instance 可能成为非 null 的,即赋值语句在对象实例化之前调用,此时别的线程得到的是一个还会初始化的对象,这样会导致系统崩溃。
假设代码执行以下事件序列:
1、线程 1 进入 getInstance() 方法。
2、由于 instance 为 null,线程 1 在 //1 处进入 synchronized 块。
3、线程 1 前进到 //3 处,但在构造函数执行之前,使实例成为非 null。
4、线程 1 被线程 2 预占。
5、线程 2 检查实例是否为 null。因为实例不为 null,线程 2 将 instance 引用返回给一个构造完整但部分初始化了的 Singleton 对象。
6、线程 2 被线程 1 预占。
7、线程 1 通过运行 Singleton 对象的构造函数并将引用返回给它,来完成对该对象的初始化。
为展示此事件的发生情况,假设代码行 instance =new Singleton(); 执行了下列伪代码:
mem = allocate(); //为单例对象分配内存空间.
instance = mem; //注意,instance 引用现在是非空,但还未初始化
ctorSingleton(instance); //为单例对象通过instance调用构造函数
//方法二:双重检验,因为每次在判断为空后才进行初始化,所以给new对象加锁就行
class Singleton3{
private static volatile Singleton3 singleton3=null;
private Singleton3(){};
public static Singleton3 getInstance(){
if(singleton3==null){
synchronized(Singleton3.class){//1
if(singleton3==null){//2
singleton3=new Singleton3();//3
}
}
}
return singleton3;
}
}
方法三:静态内部类
既实现了线程安全,又避免了同步带来的性能影响的方法
//静态内部类,避免加锁浪费时间
class Singleton4{
private static class LazySingleton{
private static final Singleton4 SINGLETON_4=new Singleton4();
}
private Singleton4(){ }
public static final Singleton4 getInstance(){
return LazySingleton.SINGLETON_4;
}
}
三、比较两种设计模式的优缺点
饿汉模式:
在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,
懒汉模式:
延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟