静态条件问题:
还没做好准备却收到了通知
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. 那么单例模式和线程有什么关系呢?涉及到线程安全问题🌂
刚才这两种单例模式的实现模式,是否是线程安全的?
(什么样的情况会导致线程不安全呢?)
- 现成的调度是抢占实质性
- 修改操作不是原子的
- 多线程同时修改同一个变量
- 内存可见性
- …
类加载只有一次机会,不可能并发执行
对于饿汉模式来说,多线程同时调用getInstance
由于getInstance只做了一件事:读取instance
所以相当于多个线程在同时读取同一个变量,不存在修改
所以他是线程安全的
对于懒汉模式来说,多线程同时调用getInstance
由于getInstance做了四件事
- 读取instance内容
- 判断instance内容是否为空
- 如果instance为null,就new实例 => 会修改instance的值
- 返回实例地址.
并发执行时:
线程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 避免内存可见性引来的问题