欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java设计模式之单例模式(懒汉,饿汉,双检锁,线程安全问题)

程序员文章站 2022-07-14 09:10:09
...

1.单例模式简单介绍:

1.特点:
    ·单例类仅有一个实例供其他对象使用
    ·单例类必须由自己创建这个唯一实例
2.单例创建:
    ·构造方法私有化 - 防止外部new对象
    ·提供public static Singleton getInstance()方法 供其他对象获取单例的实例
3.分类:
    ·饿汉式:类加载时实例化
    ·懒汉式:调用时实例化

2.代码及解释:

2.1饿汉式:常用

package pers.li.singleton;

/**
 * 饿汉式:类加载时初始化,线程安全
 *      优点:没有加锁,执行效率会提高。
 *      缺点:类加载时就初始化,分配内存。
 *      基于 classloader 机制避免了多线程的同步问题,
 */
public class HungrySingleton {

    //1.构造方法私有化
    private HungrySingleton(){}

    //2.私有静态属性
    private static HungrySingleton hungrySingleton = new HungrySingleton();

    //3.对外公开获取方法
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }
}

2.2懒汉式:线程安全-方法上加synchronized (不推荐)

package pers.li.singleton;

/**
 * 线程安全的懒汉式:
 *  1.使用synchronized关键字:
 *      1.将并行改为串行执行,保证实例化只有一次
 *      2.实例化成功后,依旧有锁,那么在并发访问时,等同于读读加锁,性能较差不推荐
 */
public class LazySingletonSafe1 {

    //1.构造方法私有化
    private LazySingletonSafe1(){}

    //2.私有静态属性
    private static LazySingletonSafe1 hungrySingleton = null;

    //3.对外公开获取方法
    public static synchronized LazySingletonSafe1 getInstance(){
        //步骤1
        if(hungrySingleton == null){
            //步骤2
            hungrySingleton = new LazySingletonSafe1();
        }
        //步骤3
        return hungrySingleton;
    }
}

2.3懒汉式-双检锁 synchronized + volatile

package pers.li.singleton;

/**
 * 线程安全的懒汉式:双检锁 + volatile
 * 1.多线程时,可能会有多个线程同时执行步骤2,所以可以考虑在步骤2加锁限制,如下
 * 2.步骤2加锁后,仍旧有问题:
 *      1.当多个线程同时执行到 synchronized时,程序由并行转为串行执行,此时已经解决了创建多个对象的问题
 *      2.当线程A执行到步骤2,线程B执行到步骤1的时候,可能会由于JVM指令重排的原因,造成步骤3中返回不完整的实例对象
 *      3.对上一步的解释:
 *          ·指令重排序:是编译器在不改变执行效果的前提下,对指令顺序进行调整,从而提高执行效率的过程
 *          ·new对象包含的过程:步骤2中  hungrySingleton = new LazySingletonSafe2();
 *              1.分配内存
 *              2.引用指向内存        hungrySingleton
 *              3.内存中存放实例      new LazySingletonSafe2()
 *          ·由上可知2,3依赖于1,所以指令重排时,2,3可能会交换,交换后会出现问题:
 *              当A线程执行到步骤2的时候,指令重排序,导致引用指向了空内存(此时1,3执行,2未执行)
 *              同时B线程到达步骤1,发现其引用指向不为null,就会返回一个不完整的实例对象
 *          ·解决方法:
 *              对私有静态属性 添加volatile关键字,保证内存可见性和防止指令重排
 *      4.综上:懒汉式线程不安全的两个问题主要来源于:
 *          1.创建多个对象    synchronized
 *          2.指令重排        volatile
 *
 */
public class LazySingletonSafe2 {

    //1.构造方法私有化
    private LazySingletonSafe2() {
    }

    //2.私有静态属性
    private static volatile LazySingletonSafe2 hungrySingleton = null;

    //3.对外公开获取方法
    public static LazySingletonSafe2 getInstance() {
        //步骤1
        if (hungrySingleton == null) {
            synchronized (LazySingletonSafe2.class) {
                //步骤2
                hungrySingleton = new LazySingletonSafe2();
            }
        }
        //步骤3
        return hungrySingleton;
    }
}