定义:确保单例类只有一个实例,并且这个单例类提供一个函数接口让其他类获取到这个唯一的实例。其构造函数应该是private类型。
使用场景:如果某个类,创建时需要消耗很多资源,即new出这个类的代价很大;或者是这个类占用很多内存,如果创建太多这个类实例会导致内存占用太多。
最简单的单例模式:
public class Singleton{
private static Singleton instance;
//将默认的构造函数私有化,防止其他类手动new
private Singleton(){};
public static Singleton getInstance(){
if(instance==null)
instance=new Singleton();
return instatnce;
}
}
多线程环境下这代码明显不是线程安全的,存在隐患:某个线程拿到的
instance
可能是
null
,可能你会想,这有什么难得,直接在
getInstance()
函数上加
sychronized
关键字不就好了。可是你想过没有,每次调用
getInstance()
时都要执行同步,这带来没必要的性能上的消耗。注意,在方法上加
sychronized
关键字时,一个线程访问这个方法时,其他线程无法同时访问这个类其他
sychronized
方法。
synchronized:
实际上,synchronized(this)以及非static的synchronized方法,只能防止多个线程同时执行同一个对象的同步代码段。
类方法中,synchronized锁住的是对象this,只有调用同一个对象的方法才需要获取锁。同时,同一个对象中所有加了synchronize的方法只能一次调用一个
static synchronized方法,static方法可以直接类名加方法名调用,方法中无法使用this,所以它锁的不是this,而是类的Class对象,所以,static synchronized方法也相当于全局锁,相当于锁住了代码段。
静态方法中,synchronized锁的是整个类对象,类似于(X.class),该类中所有加了synchronized的静态方法,一次只能调用一个
DCL双重检查锁定:
public class Singleton{
private static Singleton instance;
//将默认的构造函数私有化,防止其他类手动new
private Singleton(){};
public static Singleton getInstance(){
if(instance==null){
sychronized(Singleton.class){
if(instance==null)
instance=new Singleton();
}
}
return instatnce;
}
}
为什么需要2次判断是否为空呢?第一次判断是为了避免不必要的同步,第二次判断是确保在此之前没有其他线程进入到sychronized块创建了新实例。这段代码看上去非常完美,但是,,,却有隐患!问题出现在哪呢?主要是在instance=new Singleton();
这段代码上。这段代码会编译成多条指令,大致上做了3件事:
(1)给Singleton实例分配内存
(2)调用Singleton()构造函数,初始化成员字段
(3)将instance对象指向分配的内存(此时instance就不是null啦~)
上面的(2)和(3)的顺序无法得到保证的,也就是说,JVM可能先初始化实例字段再把instance
指向具体的内存实例,也可能先把instance
指向内存实例再对实例进行初始化成员字段。考虑这种情况:一开始,第一个线程执行instance=new Singleton();
这句时,JVM先指向一个堆地址,而此时,又来了一个线程2,它发现instance
不是null
,就直接拿去用了,但是堆里面对单例对象的初始化并没有完成,最终出现错误~ 。
优点:资源利用率高,第一次执行getInstance时单例对象才会被实例化,效率高.
最终版:
public class Singleton{
private volatile static Singleton instance;
//将默认的构造函数私有化,防止其他类手动new
private Singleton(){};
public static Singleton getInstance(){
if(instance==null){
sychronized(Singleton.class){
if(instance==null)
instance=new Singleton();
}
}
return instatnce;
}
}
volatile
关键字的作用是:线程每次使用到被
volatile
关键字修饰的变量时,都会去堆里拿最新的数据。换句话说,就是每次使用instance时,保证了instance是最新的。
public class Singleton {
private Singleton (){
}
public static Singleton getInstance(){
return SingletonHolder.SINSTANCE_SINGTON;
}
private static class SingletonHolder{
private static final Singleton SINSTANCE_SINGTON=new Singleton();
}
}