设计模式-单例模式

简介

单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。

饿汉模式

饿汉模式:顾名思义就是在程序启动后,立即创建出实例.

class Singletion {
    private static Singletion instance = new Singletion();
    // 类方法
    public static Singletion getInstance() {
        return instance;
    }

    // 做出限制,禁止通过new对象来创建实例, 但是有例外,反射可以创建出实例,但不推荐使用
    private Singletion() {

    }

}


public class Demo17 {
    public static void main(String[] args) {
        Singletion s1 = Singletion.getInstance();
        Singletion s2 = Singletion.getInstance();

        System.out.println(s1 == s2);

    }
}

在这里插入图片描述
答案是true,说明获取的确实是同一个对象.

// 此处是在Singletion类会在第一次在jvm使用时加载,也不是只在程序启动的时候.
// 进入jvm后立即创建实例
private static Singletion instance = new Singletion();

当我们不使用getInstance方法来回去实例的时候,而采用new对象的方法,就会报错.
在这里插入图片描述
饿汉模式比较简单,这里就不多赘述了,接下来让我们看懒汉模式.

懒汉模式

懒汉模式:顾名思义就是只在第一次创建实例的时候,创建实例,能不创建就不创建.

class Singletionlazy {
    private static Singletionlazy instance = null;

    public static Singletionlazy getInstance() {
        // 只有当没有实例的时候,才会new出一个实例
        if(instance == null) {
            instance = new Singletionlazy();
        }
        return instance;
    }
    // 禁止new出实例
    private Singletionlazy() {

    }
}


public class Demo18 {
    public static void main(String[] args) {
        Singletionlazy s1 = Singletionlazy.getInstance();
        Singletionlazy s2 = Singletionlazy.getInstance();

        System.out.println(s1 == s2);
    }
}

结果为: true.

可是仔细观察,饿汉模式和懒汉模式是不是哪个或者全部有问题呢?
相信你们也能看出来.懒汉模式是线程不安全的!!
在饿汉模式中,通过getInstance方法只return instance对象就可以了.
但是在懒汉模式中,还有一步new 对象的操作,多线程进行肯定时不安全的.
我画个图供赏析.

在这里插入图片描述
那我们如何解决这个问题呢?

加锁

public static Singletionlazy getInstance() {
        // 只有当没有实例的时候,才会new出一个实例
        synchronized (Singletionlazy.class) {
            if(instance == null) {
                instance = new Singletionlazy();
            }
        }
        return instance;
    }

在这里插入图片描述
这里虽然解决了new多个对象的问题,但是加锁是一个成本比较高的操作,加锁可能会引起阻塞等待.如果无脑加锁,就会使执行效率收到影响.

我们加锁了,但是后面调用的时候,都会加锁,这样很影响程序的效率.
我们说的懒汉模式线程不安全,主要是在第一次new对象的时候,后续根据if就不会进行new对象了,也就不再需要锁了.

调整加锁

public static Singletionlazy getInstance() {
        // 只有当没有实例的时候,才会new出一个实例
        if (instance == null) {
            synchronized (Singletionlazy.class) {
                if(instance == null) {
                    instance = new Singletionlazy();
                }
            }
        }
        return instance;
    }

我们在加锁之间加一个if判断,当instance为空的时候,才加锁.
这样既没有线程安全问题,程序效率也提升了.

是不是以为已经没有问题了…
考虑到加锁操作可能会阻塞,阻塞多久我们也不知道,有可能Instance此时就被更改了!!
就又出现了线程安全问题!!
也就是内存可见性问题,也是引起线程不安全的问题之一.

内存可见性和指令重排序

仔细分析,这里会不会出现内存可见性问题,只是会有这样的风险,编译器是否会采取优化,不好说,所以我们加上volatile是保险操作.
 private volatile static Singletionlazy instance = null;

此处volatile还有另外一个作用,指令重排序问题.

// 可以分为3步
instance = new Singletionlazy();
  1. 给对象创建出内存空间.得到内存地址.
  2. 调用构造方法,对对象进行初始化.
  3. 把内存地址赋给Instance引用.

如果我们在第2步之前进行第3步,还没有给对象初始化,就调度给别的线程了,
我们再利用这个对象调用方法,属性的时候,因为没有初始化,就会出现问题,
加入volatile后,就解决了这个问题.

结语

我总结了单例模式以及其中的饿汉和懒汉模式,其中懒汉模式是线程不安全的,我们需要通过加锁,双层if操作,以及volatile来解决线程安全问题.
大家多多支持,我会火力更新下一篇的->阻塞队列及实现和定时器.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值