【多线程基础】单例模式

目录

一、单例模式的定义

二、单例模式的具体实现方式

2.1 饿汉模式

2.2 懒汉模式

三、线程安全问题


一、单例模式的定义

        单例模式是指在内存中只会创建一次对象的设计模式。在程序多次使用同一对象时,为了防止频繁的创建对象而增加资源的开销,但是单例模式就可以让程序在内存中只创建一个对象,让所有需要调用的地方都共享这一单例对象。

二、单例模式的具体实现方式

单例模式的两种类型:饿汉模式和懒汉模式

2.1 饿汉模式

类加载的同时,创建实例

主要实现步骤:

1、先将构造方法私有化(private),避免直接在外部new多个对象。

2、在类的内部属性直接创建一个对象。

3、向外部提供一个公共的静态方法,用于外部引用该对象。

注意:之所以用静态方法(static),是因为类外无法直接创建对象,只能通过类名获取该类的属性和方法。

代码演示:

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

    //获取实例的方法
    public static Singleton getInstance() {
        return instance;
    }

    //禁止外部new实例
    private Singleton() { }
}

2.2 懒汉模式

类加载的时候不创建实例,第一次使用的时候才创建实例。

主要实现步骤:

1、先将构造方法私有化(private),避免直接在外部new多个对象。

2、在类的内部先声明一个对象的引用,但是不创建。

3、向外部提供一个公共的静态方法,用于创建对象并返回对象的引用。

代码演示:

class SingletonLazy {
    private static SingletonLazy instance;

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

    private SingletonLazy() {

    }
}

注意:此代码存在严重的线程安全问题

三、线程安全问题

懒汉模式会带来线程安全问题

在懒汉模式代码中,既包含了读也包含写,两步操作不是原子性导致线程不安全,所以存在线程安全问题。

举例说明:

假如有两个线程t1和t2,当t1和t2都进入该方法时,进行判断都为null,那就导致t1和t2指向的不是同一个对象了,所以就导致了线程不安全了。

解决方案:

我们可以进行加锁操作(synchronized),这样就可以保证一个线程在进行读和写操作的时候,别的线程就在锁外进行阻塞等待,这样就保证操作是原子性的了。

代码演示:

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

但是问题真的解决了嘛?

对于加锁操作来说,加锁确实可以保证线程安全,但是频繁的加锁是需要开销的,资源会浪费。

那有什么办法可以避免频发的加锁呢?

我们可以在外面再加一层if条件判断,判断是否instance被初始化完成,若被初始化完成就不必加锁了。

代码演示:

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

现在代码貌似没有问题了,但是还会存在一个内存可见性问题!

如果多个线程都去调用getInstance()方法,就会造成频繁的读instance操作,但是对于读内存和读寄存器来说,读寄存器会更高效,因此编译器会自动优化在寄存器读,也就是说即使某个线程将instance初始化了,但是由于编译器自动优化了,其它线程看起来instance还是null。

解决方案:

我们可以用volatile对instance进行修饰,被volatile修饰的变量,都会从内存中读取该变量,volatile可以禁止指令重排序!

最终代码演示:

class SingletonLazy {
    volatile private static SingletonLazy instance;

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

    private SingletonLazy() {

    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值