单例模式

单例模式可以分为懒汉式和饿汉式

懒汉式单例模式:在类加载时不初始化,不调用不初始化,一旦调用就只初始化一次。

饿汉式单例模式:在类加载时就完成了初始化,所以类加载比较慢,但获取对象的速度快。

饿汉式第一种:基于类加载机制避免了多线程的同步问题(JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的)

缺点:类加载时创建实例,比较消耗系统资源(要是应用没有使用这个实例,系统资源就白白浪费了)

/**
 * 单例模式:饿汉式,第一种
 * 缺点:类加载时创建实例,比较消耗系统资源(要是应用没有使用这个实例,系统资源就白白浪费了)
 *
 * @author :liuqi
 * @date :2018-06-13 15:13.
 */
public class SingletonDemo1 {
    private static SingletonDemo1 instance = new SingletonDemo1();

    private SingletonDemo1() {
    }

    public static SingletonDemo1 getInstance() {
        return instance;
    }
}

为了避免这种情况,我们通常使用惰性加载的机制,也就是在使用的时候才去创建。

懒汉式第一种:不能在多线程下使用

缺点:线程不安全

/**
 * 单例模式:懒汉式,第一种
 * 缺点:线程不安全
 *
 * @author :liuqi
 * @date :2018-06-13 15:17.
 */
public class SingletonDemo2 {
    private static SingletonDemo2 instance;

    private SingletonDemo2() {
    }

    public static SingletonDemo2 getInstance() {
        if (instance == null) {
            instance = new SingletonDemo2();
        }
        return instance;
    }
}

为防止多线程情况下创建多个实例的问题(A线程和B线程都进入if判断),使用Class锁机制

懒汉式第二种:在getinstance方法上加synchronized锁

缺点:线程安全但是很影响性能,每次调用getInstance方法的时候都必须获得Singleton的锁,而实际上,当单例实例被创建以后,其后的请求没有必要再使用互斥机制了

/**
 * 单例模式:懒汉式,第二种
 * 缺点:线程安全但是很影响性能,每次调用getInstance方法的时候都必须获得Singleton的锁,而实际上,当单例实例被创建以后,其后的请求没有必要再使用互斥机制了
 *
 * @author :liuqi
 * @date :2018-06-13 15:20.
 */
public class SingletonDemo3 {
    private static SingletonDemo3 instance;

    private SingletonDemo3() {
    }

    public static synchronized SingletonDemo3 getInstance() {
        if(instance == null){
            instance = new SingletonDemo3();
        }
        return instance;
    }
}

有人为了解决以上问题,提出了double-checked locking(双重检验锁)的解决方案

懒汉式第三种:双重检验锁

缺点:看似已经解决上述问题,但是对于jvm来说仍然可能会发生错误,在JDK1.5之后,双重检查锁定才能够正常达到单例效果

/**
 * 单例模式:懒汉式,第三种,双重检验锁
 * 缺点:看似已经解决上述问题,但是对于jvm来说仍然可能会发生错误,在JDK1.5之后,双重检查锁定才能够正常达到单例效果
 *
 * @author :liuqi
 * @date :2018-06-13 15:26.
 */
public class SingletonDemo4 {
    private static SingletonDemo4 instance;

    private SingletonDemo4() {
    }

    public static synchronized SingletonDemo4 getInstance() {
        if(instance == null){
            synchronized (instance){
                if (instance == null){
                    instance = new SingletonDemo4();
                }
            }
        }
        return instance;
    }
}

这样避免了进入synchronized块所需要花费的资源,其次如果两个线程同时进入了第一个if判断,那么他们也必须按照顺序执行synchronized块中的代码,第一个进入代码块的线程会创建一个新的Singleton实例,而后续的线程则因为无法通过if判断,而不会创建多余的实例。上述描述似乎已经解决了我们面临的所有问题,但实际上,从JVM的角度讲,这些代码仍然可能发生错误。

对于JVM而言,它执行的是一个个Java指令。在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例。这样就使出错成为了可能,我们仍然以A、B两个线程为例:

1.         A、B线程同时进入了第一个if判断

2.         A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();

3.         由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。

4.         B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。

5.         此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。

于是我们想到了通过内部类实现多线程环境中的单例模式

懒汉式第四种:无参构造方法,静态内部类

/**
 * 单例模式:懒汉式,第四种,无参构造方法,静态内部类
 *
 * @author :liuqi
 * @date :2018-06-13 14:15.
 */
public class SlackerSingletonNoParamStructure {
    /**
     * 私有构造方法
     */
    private SlackerSingletonNoParamStructure() {
    }
    /**
     * 创建内部类
     */
    private static class ExampleChild {
        // 内部类加载时初始化一次
        private static final SlackerSingletonNoParamStructure newInstance = new SlackerSingletonNoParamStructure();
    }
    /**
     * 提供公有访问函数
     *
     * @return SlackerSingletonNoParamStructure
     */
    public static SlackerSingletonNoParamStructure getInstance() {
        // 调用内部类 进行初始化对象操作
        return ExampleChild.newInstance;
    }
    /**
     * 测试
     *
     * @param args
     */
    public static void main(String[] args) {
        // 新建5个线程,测试getInstance方法是否获取同一个SlackerSingletonNoParamStructure对象
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(SlackerSingletonNoParamStructure.getInstance());
                }
            }).start();
        }
    }
}

懒汉式第五种:有参构造方法,静态内部类

/**
 * 单例模式:懒汉式,第五种,有参构造方法,静态内部类
 *
 * @author :liuqi
 * @date :2018-06-13 14:42.
 */
public class SlackerSingletonParamStructure {
    /**
     * 私有构造方法:有参
     */
    private SlackerSingletonParamStructure(String str) {
        System.out.println(str);
    }
    /**
     * 创建内部类
     */
    private static class ExampleChild {
        // 内部类加载时初始化一次
        private static final SlackerSingletonParamStructure newInstance = new SlackerSingletonParamStructure("testInfo");
    }

    /**
     * 提供公有访问函数
     *
     * @return SlackerSingletonParamStructure
     */
    public static SlackerSingletonParamStructure getInstance() {
        // 调用内部类 进行初始化对象操作
        return ExampleChild.newInstance;
    }

    /**
     * 测试
     *
     * @param args
     */
    public static void main(String[] args) {
        // 新建5个线程,测试getInstance方法是否获取同一个SlackerSingletonParamStructure对象
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(SlackerSingletonParamStructure.getInstance());
                }
            }).start();
        }
    }
}

饿汉式第二种:静态内部类

/**
 * 单例模式:饿汉式写法,静态内部类
 *
 * @author :liuqi
 * @date :2018-06-13 15:00.
 */
public class HungrySingleton {
    /**
     * 程序启动时就加载创建对象
     */
    private static final HungrySingleton newInstance = new HungrySingleton();

    /**
     * 私有构造方法
     */
    private HungrySingleton() {
    }
    /**
     * 提供公有访问函数
     *
     * @return Example
     */
    public static HungrySingleton getInstance() {
        return newInstance;
    }

    /**
     * 测试
     *
     * @param args
     */
    public static void main(String[] args) {
        // 新建5个线程,测试getInstance方法是否获取同一个HungrySingleton对象
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(HungrySingleton.getInstance());
                }
            }).start();
        }
    }
}

参考:https://www.cnblogs.com/jingpeipei/p/5771716.html

https://www.cnblogs.com/Ycheng/p/7169381.html

http://www.private-blog.com/2017/11/16/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%EF%BC%9A%E5%8D%95%E4%BE%8B/

代码地址:https://github.com/yuki9467/TST-javademo/tree/master/src/main/singleton

posted @ 2018-06-13 16:02  Yuki67  阅读(138)  评论(0编辑  收藏  举报