Java多线程之单例模式

目录 

前言

一、单例模式是什么

二、饿汉模式

三、懒汉模式

四、饿汉模式和懒汉模式的比较

前言

本篇博客主要介绍使用饿汉模式和懒汉模式来实现的单例模式。

一、单例模式是什么

        单例模式是一种常见的设计模式,它可以确保一个类有且仅有一个实例,并提供一个全局访问点。这在某些情况下非常有用,比如需要管理全局资源或者避免重复创建对象等。 

        设计模式:设计模式就相当于象棋中‘棋谱’的意思,针对一些特定的走法,给出来一些对策。就是一些固定套路。

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

二、饿汉模式

代码实现:

class Singleton{
    private static Singleton instance = new Singleton();//直接在类中new一个对象
    public static Singleton getInstance (){
        return instance;
    }
    private Singleton(){//将构造方法私有化,类外无法调用到构造方法,就无法new对象
    }
}

        分析:这个代码中我们直接在类内就实例化了一个对象,并且设为private方法,对外提供get方法,对于禁止类外实例化对象我们采用的是将构造方法私有化,类外无法调用到构造方法,自然也就无法实例化对象了。 饿汉模式无论是单线程还是多线程环境下,都是线程安全的。

三、懒汉模式

懒汉模式是非必要不创建对象

代码实现:

class SingletonLazy{//懒汉模式
    private static SingletonLazy instance;
    public static SingletonLazy getInstance(){
        if(instance == null){//非必要不创建
            instance = new SingletonLazy();
        }
        return instance;
    }
    private SingletonLazy(){}//构造方法私有化
}

        分析:上面的代码其实是有一定问题的,在单线程的模式下是安全的,但是在多线程的模式下就会出现线程安全问题了。这里的线程安全问题是出现在get方法里的,在get方法中,我们如果当引用instance为null时,就会去new对象,但是当有t1、t2两个线程时,如果同时在获取instance对象时,多线程执行是抢占式执行的。所以可能t1判断完instance为null后,后面的代码还没有执行,这时候cpu就去执行t2了,t2也判断完instance为null,这时候就会出现new了两个对象的,导致了对象的不唯一性,这里导致线程不安全的原因就是if操作和new操作不是原子性的。

解决方法:方法①:最简单的方法就是直接对这个方法加锁,但是因为加锁本身就是比较低效的操作,所以如果这样做的话,每一次调用这个方法的时候,就都有可能会阻塞等待。效率不高,所以我们这里不采用这种方法。

方法②:先看代码:

class SingletonLazy{//懒汉模式
    private static SingletonLazy instance;
    public static SingletonLazy getInstance() {
            synchronized (SingletonLazy.class) {
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        return instance;
    }
    private SingletonLazy(){}//构造方法私有化
}

        分析:通过最上面的分析我们可以知道线程不安全就是因为if和new操作不是原子性的,所以我们只要在if前面加上synchronized就可以保证操作的原子性了;

方法③:(方法②的优化)

代码:

class SingletonLazy{//懒汉模式
    private static SingletonLazy instance;
    public static SingletonLazy getInstance() {
        if(instance == null) {//这个判断的目的是为了看要不要加锁
            synchronized (SingletonLazy.class) {
                if (instance == null) {//这个判断是为了判断锁操作之前是否有别的线程已经new过对象了
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    private SingletonLazy(){}//构造方法私有化
}

        分析:因为我们线程安全本质上只有第一次创建对象时会出现,但是我们方法②的操作是每一次获取对象都会加锁,效率不高,因此我们的解决办法是:在锁的外面再套上一个判断,看instance是否为null,这里就会有两个一模一样的判断条件,但是他们的目的是不同的外面的if是为了第二次之后的get操作可以不用加锁,以此来提高程序的效率,而里面的锁是为了判断锁之前是否已经有线程捷足先登的把对象创建出来了。

        上面的代码我们通过三种方法来解决了if和new操作不是原子性的问题,但是这里还存在有个问题,就是指令重排序的问题。对于instance对象是存在指令重排序问题的。

详细分析:对于实例化对象的语句,一般可以分为三步:①申请内存空间;②调用构造方法,初始化内存数据;③将对象的引用赋值给s;

假设我们有线程t1和t2,当t1线程new操作按照1 3 2的顺序进行,当执行到3,2还没执行的时候,这时候另一个线程t2也调用get方法,t2的外层if判断了instance并不是null,此时就会直接返回instance的引用,这时候拿到的就是还没有初始化的对象,接下去就可能会出现问题,因此,此时就需要给instance加上volatile关键字修饰。

正确的代码:

class SingletonLazy{//懒汉模式
    volatile private static SingletonLazy instance;
    public static SingletonLazy getInstance() {
        if(instance == null) {//这个判断的目的是为了看要不要加锁
            synchronized (SingletonLazy.class) {
                if (instance == null) {//这个判断是为了判断锁操作之前是否有别的线程已经new过对象了
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
   private SingletonLazy(){}//构造方法私有化
}

四、饿汉模式和懒汉模式的比较

        首先懒汉模式和饿汉模式都是单例模式的实现方式。懒汉模式只有在第一次使用时才会创建实例,而饿汉模式则在类加载时就已经创建好实例。懒汉模式相对节省空间,但在多线程情况下会有线程安全问题。饿汉模式在某一些特定情况下会比较浪费空间。所以一般情况下更推荐使用懒汉模式。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值