单例模式(饿汉与懒汉)

前引:单例模式是校招中最常考的设计模式之⼀.

 啥是设计模式? (我感觉有点像做题的模板)

1)设计模式好⽐象棋中的"棋谱".红⽅当头炮,⿊⽅⻢来跳.针对红⽅的⼀些⾛法,⿊⽅应招的时候有⼀ 些固定的套路.按照套路来⾛局势就不会吃亏.

 2)软件开发中也有很多常⻅的"问题场景".针对这些问题场景,⼤佬们总结出了⼀些固定的套路.按照这 个套路来实现代码,也不会吃亏.

1.饿汉

饿汉:顾名思义非常饥饿,想立马吃东西。在程序代码中就是立马创建对象。

特点:

1)创建对象快 

2)只有一个实例对象(节约空间)

代码如下:

class HungrySingle{//instance 实例
    private static HungrySingle instance = new HungrySingle();
    //获取实例
    public static HungrySingle getInstance(){
        return instance;
    }
}

public class Demo1 {
    public static HungrySingle hungrySingle = null;
    
    public static void main(String[] args) {
        HungrySingle hungrySingle = HungrySingle.getInstance();
    }
}

这个时候你可能在想这和我自己new一个对象有什么区别,还不如new一个对象来的简单。

解释:

//HungerySingle类中代码与上面一个一样
public class Demo1 {
    public static HungrySingle hungrySingle = null;
    public static HungrySingle hungrySingle1 = null;
    
    public static void main(String[] args) {
        HungrySingle hungrySingle = HungrySingle.getInstance();
        HungrySingle hungrySingle1 = HungrySingle.getInstance();
        System.out.println(hungrySingle1 == hungrySingle);//true

        HungrySingle single2 = new HungrySingle();
        HungrySingle single3 = new HungrySingle();
        System.out.println(single2 == single3);//false
    }
}

 对比你会发现 hungrySingle = hungrySingle1 说明这二者是同一个,引用了同一内存,属于同一对象实例;single2 != single3,引用不同内存,属于不同对象实例。

2.懒汉(代码中细节较多)

懒汉:非常懒,只有在你万分火急的时候才会去做;在程序中就是在你调用的时候才创建对象。

特点:

1)在需要的时候创建对象 

2)只有一个实例对象(节约空间)

class LazySingle {
    private LazySingle instance = null;
    public LazySingle getInstance(){
        if(instance == null){
           instance = new LazySingle();
        }
        return instance;
    }
}
//注意:这里代码有bug
public class Demo2 {
    private static LazySingle lazySingle = null;
    public static void main(String[] args) {
        LazySingle single = lazySingle.getInstance();
    }
}

细节1:线程安全问题

解释:两个线程同时对同一个变量进行操作。与下面代码类似明明两个for循环5 0000次,count应该为10 0000,但如果你运行下面代码,你会发现结果都小于10 0000,这是为什么呢?

如图:

    private static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() ->{
            for (int i = 0;i < 50000;i++){
                count++;
            }
        });
        Thread t1 = new Thread(() ->{
            for (int i = 0;i < 50000;i++){
                count++;
            }
        });
        t.start();
        t1.start();
        t1.join();
        t.join();
        System.out.println(count);//结果小于100000
    }

 注意:循环次数不能太少,否则可能会出现t1线程都循环完了,t2才开始,就会出现正确结果。

(当然这个也能解决,通过sleep,使两个线程有重叠就OK了)

     但上面代码加锁就可以有正确结果了

public class Demo {
    private static Object locker = new Object();
    private static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() ->{
            for (int i = 0;i < 50000;i++){
                synchronized (locker){
                    count++;
                }
            }
        });
        Thread t1 = new Thread(() ->{
            for (int i = 0;i < 50000;i++){
                synchronized (locker){
                    count++;
                }
            }
        });
        t.start();
        t1.start();
        t1.join();
        t.join();
        System.out.println(count);
    }
}

细节2:指令重排序问题(这个问题发生概率是非常小的)

解释:由于JVM,CPU指令重排序造成下面这种情况,volatile关键字修饰的变量会告诉CPU这个是容易变化的,CPU就不会对其进行优化,从而解决问题。

注:instance = lazySingle

细节3:两成if嵌套有神奇效果

如果在单线程下两层if是无意义的,多线程就不一样了

1)里面一层if是用来判断instance == null,为空,则创建对象;

2)外面一层if是用来减少加锁次,加锁/解锁是⼀件开销⽐较⾼的事情.⽽懒汉模式的线程不安全只是发⽣在⾸次创建实例的时候.因此后续使⽤的时候,不必再进⾏加锁了。

正确代码

//方法一
class LazySingle1 {
    private static LazySingle instance = null;
    private static Object locker = new Object();

    //对方法加锁,在线成一调用,线程二不能调用,也就不会有指令重排序带来的问题了
    public static synchronized LazySingle getInstance(){
                if (instance == null) {
                    instance = new LazySingle();
                }
        return instance;
    }
}
//方法二
class LazySingle {
    private static volatile LazySingle instance = null;
    private static Object locker = new Object();
    public static LazySingle getInstance(){
        if(instance == null) {
            synchronized (locker) {
                if (instance == null) {
                    instance = new LazySingle();
                }
            }
        }
        return instance;
    }
}

public class Demo2 {
    private static LazySingle lazySingle = null;
    private static LazySingle lazySingle1 = null;
    public static void main(String[] args) throws InterruptedException {
       lazySingle = LazySingle.getInstance();
       lazySingle1 = LazySingle1.getInstance();
    }
}

         

               看到这里就不要吝啬手中的赞,创作不易,你的点赞时对我最大的支持!

Java单例模式包括饿汉式和懒汉式两种实现方式。饿汉式是在类加载阶段就创建实例并持有,而懒汉式则是在需要时才创建实例。 饿汉模式是指在类加载阶段就创建出实例的,因此它的实例化过程相对于普通情况要早很多。这也是为什么叫“饿汉”的原因,就像一个饥饿的人对食物没有抵抗力,一下子就开始吃了一样。 懒汉模式是指在需要时才创建实例。这种方式的优点是节省了资源,只有在需要时才会进行实例化。但是它的缺点是在多线程环境下可能会导致多个线程同时创建实例的问题,需要进行额外的线程安全措施来解决这个问题。 总结来说,饿汉式适合在应用启动时就需要创建实例的情况,因为它的实例化过程早于普通情况。而懒汉式适合在需要时才创建实例的情况,可以节省资源。 需要注意的是,单例模式的使用要根据具体的适应场景来决定,不同的情况下选择不同的实现方式。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Java设计模式之单例模式——饿汉式、懒汉式(初了解)](https://blog.csdn.net/m0_68062837/article/details/127307310)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Java多线程案例之单例模式饿汉懒汉)](https://blog.csdn.net/qq_63218110/article/details/128738155)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

茜子.Java

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值