单例模式(线程案例)

发布于:2025-03-04 ⋅ 阅读:(86) ⋅ 点赞:(0)

单例模式可以分为两种:1.饿汉模式   2.懒汉模式


一.饿汉模式    

//饿汉模式👇
class MySingleTon{

    //因为这是一个静态成员变量,在类加载的时候,就创建了
    private static MySingleTon mySingleTon = new MySingleTon();
    
    //创建一个静态方法,方便在类外获取对象
    public static MySingleTon getMySingleTon() {
        return mySingleTon;
    }

    //创建一个私有的构造方法
    //避免通过new关键字来调用构造方法,进而创建实例
    //保证了唯一性
    private MySingleTon(){

    }
    
}

public class ThreadDemo8 {
    public static void main(String[] args) {
        MySingleTon s1 = MySingleTon.getMySingleTon();
        MySingleTon s2 = MySingleTon.getMySingleTon();
        System.out.println(s1 == s2);

    }
    
}

程序运行的结果

结果是true,说明了这两次都是获取了同一个实例!,这也说明成功实现了单例模式


因为这个对象是在类加载的时候创建,类加载是单线程的,所以线程安全。但是如果在代码中,没有使用对象,就会造成资源浪费!


二.懒汉模式

这是实现懒汉模式的代码👇

class MySingleTonLazy{
    private static MySingleTonLazy mySingleTonLazy = null;

    
    public static MySingleTonLazy getMySingleTonLazy() {
        if(mySingleTonLazy != null){
            mySingleTonLazy = new MySingleTonLazy();
        }
        return mySingleTonLazy;
    }

    
    //私有的构造方法
    private MySingleTonLazy(){


    }
}

public class ThreadDemo9 {
    public static void main(String[] args) {
        MySingleTonLazy s1 = MySingleTonLazy.getMySingleTonLazy();
        MySingleTonLazy s2 = MySingleTonLazy.getMySingleTonLazy();
        System.out.println(s1==s2);
    }
}

这个代码是有两个问题的:1.线程安全问题。2.指令重排序问题

1.线程安全问题

所以说,需要对其加锁。把这些指令打包成一个原子(整体),这样就不会出现这种情况了。


这是修改后的代码,是线程安全的!

第一个if是用来判断这个实例是否已经创建了,如果创建就直接跳过这个代码了。👆


2.指令重排序问题

mySingleTonLazy = new MySingleTonLazy()

这行代码在执行的时候,会分为3个步骤:

1.分配内存空间

2.初始化对象

3.将引用指向对象

这3个步骤可以是按132顺序执行,也可以是123顺序执行👇

如果是按132来执行的,先执行完1,然后执行3 ,在执行完3后,另一个线程也来执行,因为已经创建了对象,在if判断完后,直接跳过去了,但此时这个对象没有初始化,故而会有空指针问题


那么我们该如何解决这个问题👇

引入volatile关键字

1.防止内存可见性问题

2.解决指令重排序问题

此时这个volatile 解决指令重排序问题

 private volatile static MySingleTonLazy mySingleTonLazy = null;

那么这懒汉模式正确代码如下👇

class MySingleTonLazy {
    // 使用 volatile 修饰,保证可见性和禁止指令重排序
    private volatile static MySingleTonLazy mySingleTonLazy = null;
    private static Object locker = new Object();

    public static MySingleTonLazy getMySingleTonLazy() {
        // 第一次检查,判断实例是否已经创建
        if (mySingleTonLazy == null) {
            // 加锁,保证线程安全
            synchronized (locker) {
                // 第二次检查,防止多个线程同时通过第一次检查
                if (mySingleTonLazy == null) {
                    mySingleTonLazy = new MySingleTonLazy();
                }
            }
        }
        return mySingleTonLazy;
    }

    // 私有的构造方法,防止外部实例化
    private MySingleTonLazy() {

    }
}

public class ThreadDemo9 {
    public static void main(String[] args) {
        MySingleTonLazy s1 = MySingleTonLazy.getMySingleTonLazy();
        MySingleTonLazy s2 = MySingleTonLazy.getMySingleTonLazy();
        System.out.println(s1 == s2);
    }
}

总结:

线程安全:

饿汉模式:天生都线程安全,不用如何处理

懒汉模式:线程不安全,需要自己处理

资源利用率:

饿汉模式:那么创建出了这个实例,然后并没有使用这个实例,这就造成了资源浪费的情况

懒汉模式:用的时候,创建实例,避免不必要的资源浪费的情况


网站公告

今日签到

点亮在社区的每一天
去签到