几种线程安全的单例模式

饿汉式

package com.vapy.creator.Singleton;
/**
 * 
 * @author vapy
 *
 * 饿汉式单例,线程安全
 * 
 */
public class Hungry {
    private static final Hungry singleton = new Hungry();

    private Hungry(){
    }

    public static Hungry getInstance(){
        return singleton;
    }
}

       饿汉式是最常见的两种创建单例的方法之一,它是线程安全的,但却有一个小缺点:JVM在加载这个类时就创建了它的一个实例,不管系统是否真的能用到

懒汉式

package com.vapy.creator.Singleton;
/**
 * 
 * @author vapy
 *
 * 懒汉式单例,线程安全
 *
 */
public class Lazy {
    private static Lazy singleton;

    private Lazy(){
    }

    public static synchronized Lazy getInstance() {
        if(null == singleton) {
            singleton = new Lazy();
        }
        return singleton;
    }
}

       加上synchronized关键字的懒汉式,虽然由非线程安全变为了线程安全,也同样避免了饿汉式的缺点,但它也有一个缺点:多线程不能同时调用getInstance方法

双重检验锁

package com.vapy.creator.Singleton;

/**
 * 
 * @author vapy
 * 
 * 双重检验锁,线程安全
 * 
 */
public class DoubleChecked {
    private volatile static DoubleChecked singleton;

    private DoubleChecked() {
    }

    public static DoubleChecked getInstance() {
        if (null == singleton) {
            synchronized (DoubleChecked.class) {
                if (null == singleton) {
                    singleton = new DoubleChecked();
                }
            }
        }
        return singleton;
    }
}

       双重检验锁只在首次调用getInstance时才加锁,之后每次都直接返回单例对象了,无需加锁,看起来完美的解决了懒汉式所带来的弊端。但是这里为什么要加上volatile关键字呢:

       JVM执行singleton = new DoubleChecked();并非原子操作,其实做了3件事,①为singleton分配内存②调用构造方法,创建实例③让singleton指向JVM给它分配的内存,但JVM会进行指令重排、优化的操作,这就导致了这3件事情的执行顺序不是固定的,如果JVM按照①③②的顺序进行操作的话,这会存在这样的问题:第一个线程执行完③后,singleton已经不是null了,此时第二个线程来了,它在外层检验时返回false,它会直接返回一个singleton实例,此时JVM尚未执行构造方法。问题出在了JVM的指令重排上,而volatile可以禁止JVM指令重排

       此处本人文笔有限,可能描述的不是特别清楚,对volatile关键字不是很了解的同学,请点击此处

       双重检验锁实现单例虽然没什么问题,但容易掉进JVM指令重排的陷阱中,下面写一种《Effective Java》推荐的写法

静态内部类

package com.vapy.creator.Singleton;
/**
 * 
 * @author vapy
 * 
 * 静态内部类,线程安全
 * 
 */
public class StaticNested {
    private static class NestedSingleton {
        private static final StaticNested INSTANCE = new StaticNested();
    }

    private StaticNested (){}

    public static final StaticNested getInstance() {
        return NestedSingleton.INSTANCE;
    }
}

       JVM在首次调用getInstance时会加载内部类NestedSingleton,创建一个的实例,而之后再调用getInstance时,JVM不会重复加载NestedSingleton,而且内部类是私有的,其它的类访问不了

       反射面前,毫无障碍,本文的论点都是基于不用反射的基础上的,个人认为用了反射就不存在单例模式了。单例模式的目的是为了防止创建不必要的重要对象,而造成对系统资源的浪费,能做到这一点就足够了

本文代码可在github查看:点击此处

你可能感兴趣的:(设计模式)