设计模式--单例模式

静态条件问题:
还没做好准备却收到了通知
notify是唤醒一个线程
notifyAll是唤醒所有进程
(一般更推荐notify)

wait和sleep的对比[🌂]
	这俩完全没有关系
	sleep是让当前线程休眠
	wait/notify机制一定程度的影响线程调度.

多线程的一些案例:

单例模式 是一种常见的"设计模式" 🌂

设计模式:
软件开发中,涉及到的场景有很多
也会有很多变化(软件开发是一件比较复杂的事情)
很多新手如果不加限制乱开发
此时往往就会陷入到很多很多的问题中

于是大佬们就把常见的场景的一些常见解决方案
整理成了一份棋谱
后续新手只要按照棋谱来开发.此时就不会差到哪去
这份棋谱就是设计模式

场景:

	代码中的有些概念,不应该存在多个实例,此时应该使用单例模式来解决
	保证指定的类只能有一个实例(如果尝试创建多个实例,直接编译报错)

两种典型的方式实现单例模式:

1.饿汉模式

只要类被加载,就会立刻实例化,Singleton实例
后续无论怎么操作,只要严格使用getInstance,就不会出现其他的实例
刚才的new没报错是因为Singleton类是内部类
所以类可以访问内部类的成员的
代码:

class Singleton1 {
 	// 先创建一个表示单例的类
    // 我们就要求 Singleton1 这个类只能有一个实例.
    // 再来创建一个 static 的成员, 表示 Singleton1 类的唯一的实例.
    private static Singleton1 instance = new Singleton1();
    //把构造方法变成私有,此时外部就无法访问到构造方法,也就无法new这个类实例了
    private Singleton1() {
    }
    // static 和 类相关, 和 实例无关~ 类在内存中只有一份, static 成员也就只有一份.
    public static Singleton1 getInstance() {
        return instance;
    }
}
public class EHan {
    public static void main(String[] args) {
        // Singleton1 s3 = new Singleton1();
        // 此处的 getInstance 就是获取该类实例的唯一方式. 不应该使用其他方式来创建实例了.
        Singleton1 s = Singleton1.getInstance();
        Singleton1 s2 = Singleton1.getInstance();
        System.out.println(s == s2);
    }
}
2.懒汉模式

使用懒汉模式的时候,Singleton类被加载时,不会立刻实例化
等到第一次使用这个实例的时候在实例化

如果代码一整场都没有调用getInstance
此时实例化的过程也就被省略掉了

这也被叫做"延时加载"

一般认为"懒汉模式"比"饿汉模式"效率更高
因为懒汉模式有很大可能是实例用不到,此时就节省了实例化的开销
代码:

class Singleton2 {
    private static Singleton2 instance = null;
    private Singleton2() {
    }
    public static Singleton2 getInstance() {
        if (instance == null) {
            instance = new Singleton2();
        }
        return instance;
    }
}

public  class Lanhan {
    public static  void main(String[] args) {
        Singleton2 s1 = Singleton2.getInstance();
        Singleton2 s2 = Singleton2.getInstance();
        System.out.println(s1 == s2);
    }
}

1. 那么单例模式和线程有什么关系呢?涉及到线程安全问题🌂

刚才这两种单例模式的实现模式,是否是线程安全的?
(什么样的情况会导致线程不安全呢?)

  1. 现成的调度是抢占实质性
  2. 修改操作不是原子的
  3. 多线程同时修改同一个变量
  4. 内存可见性

类加载只有一次机会,不可能并发执行
对于饿汉模式来说,多线程同时调用getInstance
由于getInstance只做了一件事:读取instance
所以相当于多个线程在同时读取同一个变量,不存在修改
所以他是线程安全的

对于懒汉模式来说,多线程同时调用getInstance
由于getInstance做了四件事

  1. 读取instance内容
  2. 判断instance内容是否为空
  3. 如果instance为null,就new实例 => 会修改instance的值
  4. 返回实例地址.
	并发执行时:
	线程1							线程2
	
	读取instance内容					读取instance内容
	
	判断instance内容是否为空			判断instance内容是否为空
	
	如果instance为null,就new实例		如果instance为null,就new实例
	
	返回实例地址						返回实例地址
	
	此时这个类已经不是单例了,而是new了两次,如果线程数目更多
	就可能会new更多的次数. 
	不再符合单例模式的要求,就认为代码是错误的

所以说懒汉模式是线程不安全的(涉及到两个线程同时去修改同一个变量)
如果要是已经把实例创建好了,后面再去并发调用getInstance就是线程安全的
线程安全的懒汉模式:

class Singleton3 {
    //使用懒汉模式时,Singleton类被加载的时候,不会立刻被实例化
    //等到第一次使用这个实例的时候再实例化.
    private static Singleton3 instance = null;
    private Singleton3() {
    }
    public static Singleton3 getInstance() {
        synchronized(Singleton3.class) {
            if(instance == null) {
                instance = new Singleton3();
            }
        }
        return instance;
    }
}

加锁以后:

线程1:						线程2

加锁						等待中..

读取instance				等待中...

判断instance内容是否为空	...

为null就new instance		...

释放锁						获取锁(instance此时一定是非null)

返回						读取instance

两种写法都可以,但是下面的写法,锁粒度更大 (锁中包含的代码越多)
就认为粒度越大

---
对于懒汉模式来说,当实例被创建之前,存在线程不安全问题
但是当实例创建好了以后,此时就不再设计线程安全问题
2. 在上面的代码中,哪怕实例已经创建好了,每次调用getInstance还是会涉及到加锁,而这里加锁解锁已经不再需要了

我们需要的是只有实例化之前调用的时候加锁,后面不加锁~
因此再次进行修改:

class Singleton3 {
    //使用懒汉模式时,Singleton类被加载的时候,不会立刻被实例化
    //等到第一次使用这个实例的时候再实例化.
    private static Singleton3 instance = null;
    private Singleton3() {
    }
    public static Singleton3 getInstance() {
        if(instance == null) {
            synchronized(Singleton3.class) {
                if(instance == null) {
                    instance = new Singleton3();
                }
            }
        }
        return instance;
    }
}

实例化前:

线程1							线程2

读取instancce					读取instancce
判断instance为空				判断instance为空
加锁(成功)						加锁(失败)
读取instancce					等待
判断instance为空
发现instance为空 new实例
释放锁
返回							加锁成功

实例化后:

线程1							线程2
读取instancce					读取instancce
判断instance非空				判断instance非空
...
3. 但是~

上面的代码仍然有问题
先加锁的线程在修改
后加锁的线程在读取
那么就涉及到内存可见性问题,因为仅仅是读取操作,可能被编译器优化
所以要在instance前加volatile保证内存可见性

class Singleton3 {
    //使用懒汉模式时,Singleton类被加载的时候,不会立刻被实例化
    //等到第一次使用这个实例的时候再实例化.
   	//添加volatile保证内存可见性
    private volatile static Singleton3 instance = null;
    private Singleton3() {
    }
    public static Singleton3 getInstance() {
        if(instance == null) {
            synchronized(Singleton3.class) {
                if(instance == null) {
                    instance = new Singleton3();
                }
            }
        }
        return instance;
    }
}

最终:🌂
为了保证线程安全,涉
及到三个要点:
1.加锁 保证线程安全
2.双重 if 保证效率
3.volatil 避免内存可见性引来的问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值