多线程|饿汉模式和懒汉模式

发布时间 2023-09-02 18:00:08作者: 司丝思

单例模式是只有单个实例的模式,应用在只能有一个实例的场景中。单例模式有很多种,这里介绍饿汉模式和懒汉模式两个单例。

一、饿汉模式

“饿汉”是一种形象的描述,“饿汉”看到吃的就非常急切,把这种急切的形象类比到Java中就是在类加载阶段就把实例创建出来了。什么是类加载?Java代码中的每个类,都会在编译完成之后得到.class文件,JVM中就会加载这个.class文件,读取其中的二进制指令,并且构造出对应的对象,这就是类加载。

实现饿汉模式:

static是实现饿汉模式的核心,static可以用来修饰成员变量和方法和成员方法,被修饰的成员变量和成员方法不依赖实例去访问,只要类被加载,通过类名就可以直接访问。由于被static修饰的成员变量和成员方法不依赖实例去访问,那么就不会因为实例的多次创建而产生多份数据,被static修饰的变量或成员在内存中只存在一份。

代码实现:

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

 

分析上述代码实例的唯一性:instance被static修饰,让instance具有类属性,在类加载之后instance在内存中只存在一份,同样的static修饰getInstance()方法,每次通过类名访问这个方法时,访问的都是同一份数据,通过这个方法得到的instance也是同一份数据,这样保证了实例的唯一性,同时,构造方法使用private修饰,保证在该类之外的代码中都不能创造新实例。

二、懒汉模式

与“饿汉模式”一样,“懒汉”也是形象的描述,与“饿汉”在类加载就创建实例不同,“懒汉”是在第一次需要的时候才创建实例。

代码是实现:

class SingletonLazy{
    private static SingletonLazy instance = null;
    public static SingletonLazy getInstance(){
        if(instance == null){
           instance = new SingletonLazy();
        }
        return instance;
    }
    private SingletonLazy(){
    }
}

懒汉模式保持实例唯一性与饿汉模式相同。

我们上述讨论的只是饿汉模式与懒汉模式怎么创建,那么多线程环境下,这两个线程是否具有线程安全问题呢?

先来看看饿汉模式

 

 饿汉模式在类加载阶段就创建好实例,多线程环境下,只涉及读操作,因此,饿汉模式多线程环境下是安全的。

 懒汉模式创建实例是在第一次使用时才创建,涉及到读和写,因此懒汉模式多线程下是不安全的。

 以上就是线程不安全的一种情况。那么如何将懒汉模式变成线程安全的呢?首先第一步是加锁,保证创建实例的过程是具有原子性的,进行如下操作:

 上述的加锁操作是让写和读具有原子性,此时多个线程要进行读和写的操作就必须等到锁被释放。上面说过,懒汉模式是第一次使用的时候需要创建实例,后面的使用就不要创建实例了,但是上述代码每次获取instance都要进行加锁操作,而加锁操作是非常消耗资源的,其实只需要在第一次创建出对象之前加锁,其他时候是没有必要的,因此,可以再给代码加上一个判定,判定instance是否为空,为空加锁创建,不为空直接返回instance。

 代码写到这,其实还存在一个问题没有考虑,那就是内存可见性问题,由于JVM的优化,只有第一次是将内存中的值读到寄存器中,而且编译器还会进行重排序。

 new操作分以下三步:

1、申请内存空间

2、调用构造方法,把内存空间初始化成一个合理的对象

3、把内存空间的地址给instance使用

正常情况下,是按照123的顺序来执行的,但是编译器会进行指令重排序来提高效率,所以new操作的顺序会被打乱,此时也会存在线程安全问题,解决办法是加上volatile关键字。

最终代码:

class SingletonLazy{
    private volatile static SingletonLazy instance = null;
    public static SingletonLazy getInstance(){
        if(instance == null){
            synchronized (SingletonLazy.class){
                if(instance == null){
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    private SingletonLazy(){
    }
}