单例模式是应用最广的模式之一。相信大家都非常熟悉了,什么,不熟悉?你都单例模式单刷了二十年了,还不懂?好吧,不懂的同学请自行百度。
首先是最经典的痴汉...不是,饿汉模式:
public class Girlfiriend extends Friend{
private static final Girlfiriend gf=new Girlfiriend ();
private Girlfiriend (){
}
public static Girlfiriend getGirlfriend(){
return gf;
}
}
饿汉模式什么都好,就是有点占地方,每次加载类的时候就存在了。有没有什么办法,用的时候在加载呢。没办法,世界是被懒人推动前进的,还真有。
懒汉模式:
public class Girlfiriend extends Friend{
private static final Girlfiriend gf;
private Girlfiriend (){
}
public static synchronized Girlfiriend getGirlfriend(){
if(gf==null){
gf= new Girlfriend();
}
return gf;
}
}
这种方法实现了在需要的时候加载。但是注意这里的synchronized关键字,synchronized 保证了当进行多线程操作的时候不会产生多个实例。但是这种做法有一个缺点,不管是不是已经存在实例了,都会被锁阻塞。
那么,有没有什么办法解决呢。这个世界是由聪明人改善,所以当然有!
当当当当,DCL模式来了。DCL是不是听起来特别高大上,其实就是Double Check Lock,说人话就是,你瞅两次看看到底有没。
public class Girlfiriend extends Friend{
private static final Girlfiriend gf;
private Girlfiriend (){
}
public static Girlfiriend getGirlfriend(){
if(gf==null){
synchronized(Girlfriend.class){
if(gf==null){
gf= new Girlfriend();
}
}
}
return gf;
}
}
是不是感觉这就结束了。一篇好的文章是要出其不意的。所以,没完。
DCL并不是十分稳定的,由于java编译器允许处理器乱序执行,所以这样做是有隐患的。
简单说,其实new对象的操作不是原子性的。这句代码最终会被编译成多条汇编指令。
(1)给Girlfriend的实例分配内存
(2)调用Girlfriend()的构造函数,初始化成员字段
(3)将gf对象指向分配的内存空间
也就是说这三条指令顺序是不可预知的。当另一个线程执行第一个if(gf==null)的时候,由于已经调用了构造函数,但是构造还没有完成。会把没有构造完全的对象返回。
那么怎么解决呢,聪明的你一定也能想到,只不过聪明人太多,有人先想到了:
静态内部类单例模式:
public class Girlfiriend extends Friend{
private Girlfiriend (){
}
public static Girlfiriend getGirlfriend(){
return GirlfriendMother.gf;
}
private static class GirlfriendMother{
private static final Girlfiriend gf= new Girlfiriend ();
}
}
第一次加载Girlfriend的时候,gf不会被初始化,(java类只有被调用的时候才会初始化)。这种方式不仅能够确保线程的安全,也能够保证单例对象的唯一性,同时也延迟了单例的实例化。所以这才是推荐使用的单例模式实现的方式。
嘿嘿,你以为到这就完了?too young too naive:
最后是一种单例模式的终极形态——枚举单例:
public enum Girlfriend{
GIRLFRIEND;
}
默认枚举实例的创建是线程安全的,并且在任何情况下它都是一个单例。
网友评论