多线程5(单例模式)

1.什么是单例模式

2.单例模式的分类及其注意事项


1.什么是单例模式

简单说单例模式就是一个类始终只有唯一的一个对象,无法创建多个对象

为什么要有单例模式,也就是不能创建多个对象,是为了解决资源浪费,数据混乱,状态不一致等问题,数据混乱比如不同的对象中,有一个对象对数据进行了修改,其他对象不一定能同步,可能会继续使用旧值,状态不一致常见登录状态,登录状态无法保持全局一致


2.单例模式的分类

单例模式分为饿汉模式和懒汉模式---------实现单例模式的关键就在于构造方法私有化

(1)饿汉模式

java 复制代码
public class Singleton {

    //final防止二次赋值
    //static修饰变为类属性,类加载就会被创建
    private final static Singleton instance = new Singleton();//类加载就创建实例

    public static Singleton getInstance(){
        return instance;
    }
    private Singleton(){

    }

    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2);
    }
}

饿汉模式就是非常饥渴,上来不管三七二十一先把对象创建了再说,哪怕你暂时用不到

那么就会出现问题,

1.1 浪费,而且如果初始化的对象数据庞大,那么将降低效率

1.2 无法延迟加载,比如你的项目原本要依赖某些配置文件,但还未配置好就加载项目了,会造成bug

但是饿汉模式天然线程安全,只涉及到读的操作


(2)懒汉模式

需要创建对象时才会实例化,涉及到条件判定+赋值的操作,因此懒汉模式会造成线程不安全问题

懒汉优点:支持懒加载,节省内存,适合大对象初始化

懒汉模式的线程不安全就在于首次创建对象时会产生线程不安全

需要注意三点

1.加锁

2.避免无效加锁

3.指令重排序问题

java 复制代码
public class SingletonLazy {

    //添加volatile避免指令重排序
    private volatile static SingletonLazy instance = null;//类加载时并不会创建对象

    /*//懒汉模式的关键在于,把实例化的创建时机推迟,推迟到第一次使用才创建
    public static SingletonLazy getInstance(){
        //进来先判断是否创建过,没创建过就创建,已经创建过就返回之前的
        if(instance == null){
            //但这里if判断与进来后赋值存在线程不安全的问题,这两者不是原子的,虽然说两个线程错开来创建了对象,
            // 最后真正的对象是其中一个线程的最后赋值指令,另外一个线程创建的对象没有被引用会被自动回收,
            // 但存在的问题在于如果对象构造加载的数据非常大呢?就相当于加载了多余的一份数据
            instance = new SingletonLazy();
        }
        return instance;
    }*/

    /*public static Object object = new Object();
    public static SingletonLazy getInstance(){
        //这样一进入方法就加锁,加锁太多次了,效率低
        synchronized (object){
            if(instance == null){
                instance = new SingletonLazy();
            }
        }
        return instance;
    }*/

    public static Object object = new Object();
    public static SingletonLazy getInstance(){
        //这里判断是否要加锁
        if(instance == null){
            synchronized (object){
                //这里判定是否要实例化
                if(instance == null){
                    instance = new SingletonLazy();
                }
            }
        }

        return instance;
    }

    //单例模式的主要点在于不能让构造方法被调用,所以构造方法得设置为私有的
    private SingletonLazy(){

    }

    public static void main(String[] args) {
        SingletonLazy s1 = SingletonLazy.getInstance();
        SingletonLazy s2 = SingletonLazy.getInstance();
        System.out.println(s1 == s2);

    }
}

1.加锁:由于判断和赋值不是原子的,加上CPU的随机调度,必然会产生线程不安全,所以要使用synchronized对这两个操作进行加锁,把他们变为原子的

2.避免无效加锁,就像上述代码,虽然已经进行了加锁,但每次需要获取到唯一对象时,只要一进入getinstance方法就会无脑加锁,要加锁又要解锁,开销大,降低效率,所以我们要再加上一个条件,进入这个方法时就要判断有没有实例化过对象,如果实例化过了就跳过这个加锁的过程,直接返回对象即可

所以我们能不加锁就不要加锁

3.指令重排序问题

指令重排序和内存可见性一样,都属于编译器优化的一种手段:调整指令执行的顺序,从而提高代码效率

就拿代码中的赋值对象语句: instance = new SingletonLazy()来说

它还能细分为三小步

(1)分配内存空间

(2)针对空间进行初始化

(3)内存空间首地址,赋值到引用变量中

正常来讲顺序为123 但由于指令重排序,可能会变为132,那么最后拿到的却是被初始化的对象,也就是一个null,相当于方便面先封口,再去装调料包,那么最后得到的是一包没有调料包的方便面

所以我们避免这个情况,使用volatile来对instance进行修饰,告诉编译器,对instance进行操作时,不需要对其进行优化

相关推荐
断眉的派大星3 天前
单例模式使用
单例模式
CoderMeijun5 天前
C++ 单例模式:饿汉模式与懒汉模式
c++·单例模式·设计模式·饿汉模式·懒汉模式
A.A呐7 天前
【C++第二十八章】单例模式
c++·单例模式
沉淀粉条形变量8 天前
rust 单例模式
开发语言·单例模式·rust
Lyyaoo.8 天前
【JAVA基础面经】线程安全的单例模式
java·安全·单例模式
zhaoshuzhaoshu9 天前
设计模式之创建型设计模式详细解析(含示例)
单例模式·设计模式·架构
梦游钓鱼9 天前
c++中单例模式(局部静态变量)
开发语言·c++·单例模式
游乐码9 天前
c#单例模式
单例模式·c#
Albert Edison12 天前
【C++11】特殊类设计
开发语言·c++·单例模式·饿汉模式·懒汉模式