单例模式初了解

本文介绍了Java中的单例模式,包括饿汉模式和懒汉模式的实现,探讨了它们的线程安全问题。饿汉模式在类加载时即创建实例,线程安全,而懒汉模式在首次调用时创建,可能存在线程不安全。为了解决懒汉模式的线程安全问题,可以使用synchronized关键字,同时通过双重检查锁定(Double-Check Locking)进一步提高效率。此外,volatile关键字也能确保多线程环境下变量的可见性。
摘要由CSDN通过智能技术生成

单例模式

单例模式是一种设计模式,
  所谓设计模式,就像棋谱一样,针对了一些特定的场景,已经产生了对应的解决方案。

比如:
  我们在学习JDBC的时候创建的一个数据源DataSource就应该是一个单例。
  还有,在实际开发中还有一些负责加载数据到内存的类,也应该是单例,
  因为这样的类一般有几个G,甚至十几个G,如果不是单例的话,每次运行都要占用大量的内存,

单例模式相当于从语法的角度,强制规定,某一个类只能够有一个实例。

单例模式的实现

  单例模式的实现主要依托于static关键字~(static关键字修饰的成员,称之为静态成员),
  在Java中,一个成员被static修饰后,他就变成了一个“类属性”,而非一个“实例属性”,我们在调用它的时候只能通过:类名.静态成员 的方式去调用,,而不是通过创建类的实例对象来调用。

单例模式的风格

饿汉模式

饿汉,饿了一次想吃三大碗

/**
 * @Name: ThreadDemo1
 * @Description:     饿汉模式
 * @Author: panlai
 * @Date: 2021/8/10 10:00
 */

public class ThreadDemo1 {
    //Singleton类是一个单例类,只有一个实例
    static class Singleton{
        //创建一个成员,保存唯一的一个Singleton实例
        private static Singleton instance = new Singleton();
        //通过public方法来获取到这个实例
        public static Singleton getInstance(){
            return instance;
        }
        //再将类的构造方法设为私有的,以避免其他成员构建类的实例
        private Singleton(){};
    }


    public static void main(String[] args) {
        Singleton s = Singleton.getInstance();
    }
    
}

懒汉模式

懒汉,懒得动,吃几碗就洗几个碗。

/**
 * @Name: ThreadDemo2
 * @Description:        懒汉模式
 * @Author: panlai
 * @Date: 2021/8/10 10:07
 */

public class ThreadDemo2 {
    //懒汉模式的实现
    //创建实例的时机是第一次使用getInstance方法的时候,就比饿汉模式更晚
    static class Singleton {
        private static Singleton instance = null;
        public static Singleton getInstance(){
            //只有当使用instance实例的时候才创建,并非一开始就创建好。
            if (instance == null){
                instance = new Singleton();
            }
            return instance;
        }
        private Singleton(){}
    }

    public static void main(String[] args) {
        Singleton s = Singleton.getInstance();
    }
    
}

一般认为懒汉模式更好(但也不是绝对,主要还是看使用的场景,只不过懒汉模式更高效一些)。
因为:

饿汉模式:(立刻洗碗)吃完饭吃了三碗饭,就洗三个碗,
懒汉模式:吃了三碗饭,放着不洗,下次吃饭时要吃几碗就洗几个碗。(节约成本)

Linux命令less命令就是采用了懒汉模式,
每次打开一个文件,只加载眼前用到的这一屏幕数据到内存中,所以打开速度就比较快,翻页时在重新加载,
但是如果采用记事本打开,就会把所有的数据都加载出来,然后才能被看到,效率就比较低。

单例模式线程安全问题

上面两种中哪种是线程安全的?哪种是线程不安全的?
答案是:懒汉模式存在线程安全问题。
上述代码中懒汉模式的代码执行流程大致分为以下四个步骤:
在这里插入图片描述

但是如果两个线程是以这样的方式执行,
我们会发现,线程1在new了实例之后,还没有进行save时,线程2就又创建了一个实例,这整个过程当中创建了两个实例,不符合单例模式思想。()
在这里插入图片描述

那么该如何保证线程安全呢?

加锁 synchronized
参考:如何保证线程安全?
如果把synchronized加到getInstance方法外边,这个时候相当于内部的所有操作都是串行的,
如果加到方法内部,就只针对,判断,new操作是串行的,提高了效率(串行不如并行效率高)。

/**
 * @Name: ThreadDemo3
 * @Description:        加锁解决懒汉线程不安全
 * @Author: panlai
 * @Date: 2021/8/10 10:33
 */

public class ThreadDemo3 {
    //懒汉模式的实现
    //创建实例的时机是第一次使用getInstance方法的时候,就比饿汉模式更晚
    static class Singleton {
        private static Singleton instance = null;
        //完全可以吧synchronized关键字加到getinstance方法前,但是这样不太好,过于粗暴
        public static Singleton getInstance(){
            //如果把关键字写在内部,就减少了串行的代码,提高了效率
            synchronized(Singleton.class){
                if (instance == null){
                    instance = new Singleton();
                }
            }
            return instance;
        }
        private Singleton(){}
    }
}

  但是,这样做也无法逃脱一个不变的定律:
    加锁注定与高效无缘。
  但是上面的方法还有可以有优化的地方,因为只有实例没有被初始化,需要进行创建实例时需要进行加锁操作,所以如果按照上面的方法,就会造成不必要的加锁解锁操作,浪费时间,浪费资源。
  所以只需要在加锁前在进行一个if条件判断如果实例已经被初始化了,就不需要进行第二个加锁判断操作了。
如下:

        public static Singleton getInstance(){
            //如果把关键字写在内部,就减少了串行的代码,提高了效率
            if (instance == null){
                synchronized(Singleton.class){
                    if (instance == null){
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }

第一次if判断是为了判断这个线程是否被初始化,如果已经被初始化,就不需要进行加锁操作了。如果没有被初始化,则才进行加锁,并且进行初始化操作。,提高了效率,节省了时间。

此外,在多线程调用该方法时,还需要使用volatile关键字,来摆正线程读到的值都是内存中最新的值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值