单例模式可以说是除了工厂模式以外最最最让人们熟知的设计模式了。
他的特点很简单,就是一个类只有唯一一个实例对象,一般对于比较消耗资源由没有配置上的变动的对象,就比较适合单例模式,Spring Bean的默认就是单例。像而且为了防止有一些不必要的线程同步,单例模式都是比较好的,比如win下的任务管理器。
实现单例模式的方式也是五花八门的,整体分为懒汉式和饿汉式。
懒汉式
懒汉,懒汉顾名思义,不到必要的时候就不初始化,概括点说就是延迟加载。实现方式有经典的双重检测,静态内部类也算是懒汉式,因为静态内部类是延后加载的。
延迟加载只有需要的时候才会去加载它,这就避免了一些不必要的浪费。
双重检测:
因为new操作不是一个原子操作,分为1.开辟内存空间,2.给空间赋值,3.将引用指向这片内存空间。如果在单线程的场景下,2和3之间没有依赖,顺序可被优化,影响不大。但是多线程的场景下,就可能如果虽然引用指向了那个内存,但是并没有被初始化,所以仍然是NULL,所以为了防止这种重排序,需要加上volatile关键字,禁止重排序。
第一个if是为了防止已经初始化完成后再重复获取锁,耗费不必要的资源(一方面由于加锁操作这个步骤单线程化,另一方面从核心态到用户态之间的切换也挺耗费资源的)。
第二个if的作用还是因为new操作是个复合操作,所以可能第一次检测时,对象未新建完成,这时阻塞于synchronized,当初始化完成,被阻塞的线程就都被唤醒重新尝试获取锁了。但是这时为了防止重复创建对象,所以需要再加一层判断。
private static Singleton singleton;
private Singleton(){
}
public static Singleton getInstance() {
if(singleton==null){
synchronized (Singleton.class){
if(singleton==null){
singleton = new Singleton();
}
}
}
return singleton;
}
静态内部类
由于静态内部类也是仅在需要时才会去加载它。也就说可以延后加载。不过内部类传参不方便。
但是它效率比前一种高一点,竞争的是初始化锁,也没有多于判断。
private Singleton(){
}
private static class SingletonHelper{
private static Singleton singleton;
static {
singleton = new Singleton();
}
}
public static Singleton getInstance() {
return SingletonHelper.singleton;
}
没有应用价值的CAS方式:
虽然是只发布了一个对象,但是需要注意的是,可不止创建了一个对象。
private volatile static AtomicReference<Singleton> singleton = new AtomicReference<>();
public static Singleton getInstance(){
for(;;){
if(singleton.get()==null){
Singleton singleton = new Singleton();
Singleton.singleton.compareAndSet(null,singleton);
System.out.println("新建了一个对象");
}else{
return singleton.get();
}
}
}
饿汉式
名字很形象,不管三七二十一,先初始化再说。优势,代码简单,效率也还可以,缺点不能延迟加载。
传统实现:
private static Singleton singleton = new Singleton();
public static Singleton getInstance() {
return singleton;
}
枚举实现:
public enum Singleton{
SINGLETON;
}