多线程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进行操作时,不需要对其进行优化

相关推荐
HEADKON3 天前
司拉德帕失代偿期肝硬化及胆道梗阻患者禁止使用,肝酶升高需暂停药物
单例模式
IT空门:门主5 天前
Java 单例模式详解:7 种实现方式 + volatile 原理 + 反射与序列化问题
java·开发语言·单例模式
码农的小菜园5 天前
Java创建单例
java·开发语言·单例模式
yunn_5 天前
单例模式两种实现方法
开发语言·c++·单例模式
Shan12055 天前
浅谈:单例模式的弊端与对策
单例模式
I Promise347 天前
C++ 单例模式超详细讲解
开发语言·c++·单例模式
阿文的代码库7 天前
C++的单例模式及其作用
开发语言·c++·单例模式
Shan12057 天前
一文读懂:C++中单例模式的实现
java·c++·单例模式
老码观察7 天前
设计模式实战解读(一):单例模式——全局唯一实例的正确打开方式
单例模式·设计模式
mingshili7 天前
[Python] Python中自带模块级的单例模式-不需要定义单例类
python·单例模式