单例模式    简单讲解

        单例模式适合用于频繁创建的对象,重量级对象更应该采用

        如果不采用单例模式,每个配置文件的内容都是一样的,

        创建重复的对象 会浪费内存,
        单例模式 每次只获取一个对象

        减小内存开销和GC压力




package 饿汉模式;

/**
 *
 */
public class HungryMode {

    /**
     * 所谓 饿汉式 单例,就是有一个私有的构造方法
     * + 私有的静态当前类实例  +  公有的静态获取实例方法
     * 由于类实例对象为 静态变量,加载类时 会创建实例对象
     *
     * 饿汉 故名思意,很饿,不管有多少 都要吃掉
     *
     * 这样会比较消耗内存
     *
     * 按需加载 且加载一次
     *
     * 优点:类加载时 完成实例化,避免线程同步问题
     * 缺点:类加载时完成实例化,但是 没有达到Lazy Loading效果,如果从未使用,亏
     */
    private static HungryMode sHungryMode = new HungryMode();

    private HungryMode(){
        System.out.println("creat" + getClass().getSimpleName());
    }

    public static void fun(){
        System.out.println("call fun in HungryMode");
    }

    public static HungryMode getInstance(){
        return sHungryMode;
    }

    public static void main(String[] args) {
        HungryMode.fun();
        System.out.println(HungryMode.getInstance());
    }
}

package 懒汉模式;

/**
 * 当 这个对象构造方法很复杂,这样的单例写法会造成 类加载很慢,会浪费很多性能
 *
 * 需要懒加载,所谓的懒汉式加载
 *
 *
 * 真正需要的时候 才创建实例   不适用多线程
 */
public class LazyMode {

    private static LazyMode sLazyMode;

    private LazyMode(){
        System.out.println("create " + getClass().getSimpleName());
    }

    private static LazyMode getInstance(){
        if(sLazyMode == null){
            sLazyMode = new LazyMode();
        }
        return sLazyMode;
    }

    public static void main(String[] args) {
        System.out.println(LazyMode.getInstance());
    }

}

多线程懒汉(懒汉基础上进行)优化一

package 多线程的单例;

/**
 * 多线程的单例,这个单例 在 懒加载的基础上 来进行改造
 *
 * 多线程 会出现多个对象,需要在 获取类实例上加上““同步锁””
 *
 * 并且 给类实例对象加上 volatile修饰符,能保证对象的可见性
 *      即在工作内存的内容更新 能立即在主内存中可见,
 *      工作内存:各个线程独有的内存
 *      主内存:所有线程共享的内存
 *      还有一个作用:(禁止指令重排序优化)
 *              有的编辑器 会进行优化,执行顺序 跟 代码顺序不同
 *              这在单线程看起来可以,但是多线程不行,volatile从语义上解决了这个问题
 */
public class LazyMode {

    private static volatile LazyMode sLazyMode;

    private LazyMode(){
        System.out.println("create " + getClass().getSimpleName());
    }

    //性能问题,多个线程获取实例对象 会排队等待 获取锁
    //其实没必要,大多数 已经创建成功了,不用进入加锁的代码快
    //进行优化, 双重校验的单例模式
    public static LazyMode getInstance(){
        synchronized (LazyMode.class){  //同步代码快
            if(sLazyMode == null){
                sLazyMode = new LazyMode();
            }
        }
        return sLazyMode;
    }

    public static void main(String[] args) {
        LazyMode.getInstance();
    }
}

优化二:(双重校验)

package 多线程的单例;

/**
 * 多线程的单例模式   采用双重校验机制
 *
 * 效率 和 安全 双重保证
 *      JDK1.5后 禁止指令重排优化
 */
public class DoubleCheckMode {

    private volatile static DoubleCheckMode sDoubleCheckMode;

    public DoubleCheckMode(){
        System.out.println("create " + getClass().getSimpleName());
    }

    public static DoubleCheckMode getInstance(){
        if(sDoubleCheckMode == null){
            synchronized (DoubleCheckMode.class){
                if(sDoubleCheckMode == null){
                    sDoubleCheckMode = new DoubleCheckMode();
                }
            }

        }
        return sDoubleCheckMode;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(){

                @Override
                public void run() {
                    super.run();
                    System.out.println("thread" + getId());
                    DoubleCheckMode.getInstance();
                }
            }.start();
        }
    }
}

静态内部类

package 静态内部类实例单例;

/**
 * 利用静态类 只会加载一次的机制
 * 使用 静态内部类 持有单例模式
 *
 * 保证 多线程的对象唯一性,还有 提升性能,不用同步锁机制
 */
public class InnerStaticMode {

    private static class SingleTonHolder{
        //静态类
        public static InnerStaticMode sInnerStaticMode =new  InnerStaticMode();

    }

    public static InnerStaticMode getInstance(){
        return SingleTonHolder.sInnerStaticMode;
    }
}

最推荐的一种(枚举)

package 枚举;

/**
 * 利用枚举的方式 实现单例
 *
 * Android不推荐
 *
 * Effective JAVA 推荐方法
 *
 * 保证线程安全和单一实例的问题
 */
public enum  EnumMode {

    INSTANCE;

    private int id;

    public int getId(){
        return id;
    }

    public void setId(int id){
        this.id = id;
    }

    public static void main(String[] args) {
        EnumMode.INSTANCE.setId(1);
        System.out.println(EnumMode.INSTANCE.getId());
    }
}

(1)、当我们写了 new 操作,JVM 到底会发生什么?

首先,我们要明白的是: new Singleton3() 是一个非原子操作。代码行singleton3 = new Singleton3(); 的执行过程可以形象地用如下3行伪代码来表示:

memory = allocate();        //1:分配对象的内存空间
ctorInstance(memory);       //2:初始化对象
singleton3 = memory;        //3:使singleton3指向刚分配的内存地址

但实际上,这个过程可能发生无序写入(指令重排序),也就是说上面的3行指令可能会被重排序导致先执行第3行后执行第2行,也就是说其真实执行顺序可能是下面这种:

memory = allocate();        //1:分配对象的内存空间
singleton3 = memory;        //3:使singleton3指向刚分配的内存地址
ctorInstance(memory);       //2:初始化对象

这段伪代码演示的情况不仅是可能的,而且是一些 JIT 编译器上真实发生的现象。

(2)、重排序情景再现

了解 new 操作是非原子的并且可能发生重排序这一事实后,我们回过头看使用 Double-Check idiom 的同步延迟加载的实现:

我们需要重新考察上述清单中的 //3 行。此行代码创建了一个 Singleton 对象并初始化变量 singleton3 来引用此对象。这行代码存在的问题是,在 Singleton 构造函数体执行之前,变量 singleton3 可能提前成为非 null 的,即赋值语句在对象实例化之前调用,此时别的线程将得到的是一个不完整(未初始化)的对象,会导致系统崩溃。下面是程序可能的一组执行步骤:

1、线程 1 进入 getSingleton3() 方法;
2、由于 singleton3 为 null,线程 1 在 //1 处进入 synchronized 块;
3、同样由于 singleton3 为 null,线程 1 直接前进到 //3 处,但在构造函数执行之前,使实例成为非 null,并且该实例是未初始化的;
4、线程 1 被线程 2 预占;
5、线程 2 检查实例是否为 null。因为实例不为 null,线程 2 得到一个不完整(未初始化)的 Singleton 对象;
6、线程 2 被线程 1 预占。
7、线程 1 通过运行 Singleton3 对象的构造函数来完成对该对象的初始化。

显然,一旦我们的程序在执行过程中发生了上述情形,就会造成灾难性的后果,而这种安全隐患正是由于指令重排序的问题所导致的。让人兴奋地是,volatile 关键字正好可以完美解决了这个问题。也就是说,我们只需使用volatile关键字修饰单例引用就可以避免上述灾难。

package 多线程的单例;

/**
 *  借助 ThreadLocal 的线程安全 的懒汉式单例
 */
public class Singleton4 {

    //ThreadLocal 线程局部变量
    private static ThreadLocal<Singleton4> threadLocal = new ThreadLocal<>();
    private static Singleton4 singleton4 = null;

    private Singleton4(){}

    public static Singleton4 getSingleton4(){
        if(threadLocal.get() == null){ //第一词检擦:该线程是否第一次访问
            createSingleton4();
        }
        return singleton4;
    }

    public static void createSingleton4(){
        synchronized (Singleton4.class){
            if(singleton4 == null){     //第二次检擦,是否被创建
                singleton4 = new Singleton4();//只执行一次
            }
        }
        threadLocal.set(singleton4);//将单例 放入当前线程的局部变量中
    }

}