单例的三大要点:
饿汉式单例模式是最基础的,这里我就不再过多讲解;
特点:
class Singleleton {
//注意这里必须是private static final
private static final Singleleton single = new Singleleton();
private Singleleton() { }
public static Singleleton getSingle(){
return single;
}
}
所谓懒汉式,就是等到调用获取单例方法时再new对象进行返回;
特点:
class Singleleton {
private static Singleleton single = null;
private Singleleton() { }
public static Singleleton getSingle(){
if(single == null) {
single = new Singleleton();
}
return single;
}
}
下面的代码就是对上面的进行了加锁设置,这样一来,当多线程访问时,就只能有一个线程对其访问,从而保证了单个实例的产生;
特点:
class Singleleton {
private static volatile Singleleton single = null;
private Singleleton() { }
public static Singleleton getSingle(){
synchronized (Singleleton.class) {
if(single == null) {
single = new Singleleton();
}
}
return single;
}
}
双重检验锁模式(double checked locking pattern),是一种使用同步块加锁的方法。程序员称其为双重检查锁,因为会有两次检查 instance == null,一次是在同步块外,一次是在同步块内。
为什么要在同步块外加一次判断?
因为加上这次判断,就会解决上面那个版本的问题,这样一来,很多线程访问时就不用再等待锁,这样极大的提高了效率;
为什么在同步块内还要再检验一次?
因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了
看似多此一举,但实际上却极大提升了并发度,进而提升了性能 !!!!!!!!!!
class Singleleton {
private static volatile Singleleton single = null;
private Singleleton() { }
public static Singleleton getSingle(){
if(single == null) {
synchronized (Singleleton.class) {
if(single == null) {
single = new Singleleton();
}
}
}
return single;
}
}
在这里我再着重讲解一下为啥要在定义的时候加上volatile关键字 :(jdk5后)
这段代码看起来很完美,很可惜,它是有题。主要在于instance = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。
- 给 instance 分配内存
- 调用 Singleton 的构造函数来初始化成员变量
- 将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)
但是在 JVM 的即时编译器中存在指令重排序的优化 。也就是说上面的二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。 我们只需要将 instance 变量声明成volatile 就可以了。
可见:volatile关键字在这里的作用是:禁止指令重排序; ⭐
对于volatile关键字,下面一篇博客我会对它进行专门的介绍!
那么,有没有一种延时加载,并且能保证线程安全的简单写法呢?我们可以把Singleton实例放到一个静态内部类中,这样就避免了静态实例在Singleton类加载的时候就创建对象,并且由于静态内部类只会被加载一次 ,所以这种写法也是线程安全的:
class Singleleton {
private static class Inner {
static Singleleton single = new Singleleton();
}
private Singleleton() { }
public static Singleleton getSingle(){
return Inner.single;
}
}
其实这种写法和饿汉式是大同小异;
对于上面三种方式而言,都还存在着一点点缺陷:
这里提出了一种新的方法产生单例,使用枚举!!!
public enum Singleton {
INSTANCE;
private String name;
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}
这种方式在不同平台有不同的支持度。