notify()⽅法
notify ⽅法 是 唤醒等待的线程.


此外要注意


这两个要锁的是同一个对象
否则将不会产生任何影响

notifyAll
notify⽅法只是唤醒某⼀个等待线程.使⽤notifyAll⽅法 可以⼀次唤醒所有的等待线程.

虽然从结果是同时唤醒 t1 t2
但是实则不然
wait唤醒后重新加锁
为其中一个线程加锁 另一线程阻塞等待
要等待先前 的先执行完成后 后者才能加锁
如果只调用某一个线程效果如下:

当多个线程 在同一对象使用wait
notify会随机调度一个线程的
wait 和sleep的对⽐
1.wait 需要搭配 synchronized 使⽤. sleep 不需要.
)如果都在synchronized内使用
那sleep 将不会释放锁(抱锁💤)
其他线程无法得到这个锁
2.wait 是 Object 的⽅法sleep 是 Thread的静态⽅法.
wait 可使用 notify 提前唤醒
sleep 可使用 Interrput 提前唤醒
单例模式
单例模式 是最常见的 设计模式之⼀
(简单地说 在面对软件开发中所出现的各种问题 先人总结了一些方法 按照这个套路来实现代码)
单例模式能保证某个类在程序中只存在唯⼀⼀份实例,⽽ 不会创建出多个实例.
单例模式具体的实现⽅式有很多.最常⻅的是**"饿汉"** 和 "懒汉" 两种
饿汉模式
Singleton
类加载的同时,创建实例.

懒汉模式
SingletonLazy
懒汉方式却恰恰相反
(遵循着非必要不创建)
但在多线程中 饿汉 和 懒汉 又有什么不一样的吗?
饿汉
在饿汉模式 String 是 天然安全的 不涉及 到线程安全
懒汉
但在 多线程中 上述实现是线程不安全的.
线程安全问题发⽣在⾸次创建实例时.
如果在多个线程中同时调⽤getInstance⽅法,就可能导致创建 出多个实例.
所以在处理线程安全中的常规手段 要加上 synchronized 可以改善这⾥的线程安全问题.
修改是原子的 操作
所以修改是要把 要修改的对象打包成 原子性的
但是在加锁前也是要确认 当前的对象是否需要加锁
加锁/解锁是⼀件开销⽐较⾼的事情.⽽懒汉模式的线程不安全只是发⽣在⾸次创建实例的时候.因此 后续使⽤的时候,不必再进⾏加锁了.
在多线程懒汉模式 还有可能引起 **"内存可见性"**的问题
当 T1 线程 在 读写时 T2 线程修改
private static volatile SingletonLazy instance = null;
加入volatile 1.确保从内存中完成读取操作
2.关于该变量的修改不会涉及到读取和修改操作 ,不会触发重排序
编译器会调整 代码执行顺序 指令重排序
在编译逻辑不变的前提之下 将代码的执行顺序改变以此来优化执行性能和效率
此外上述图中还涉及到了一点 双重if的使用
外层的if就是判定下看当前是否已经把instance实例创建出来了
多线程⾸次调⽤getInstance, 其他线程可能都发现instance 为 null,
于是⼜继续往下执⾏来竞争锁,其 中竞争成功的线程,再完成创建实例的操作.
创建实例后 其他竞争到锁的线程就被 ⾥层if 挡住了.也就不会继续创建其他实例
阻塞队列
是一种特殊的队列
1.线程安全
2.阻塞队列特性 遵守"先进先出"的原则.
当队列满的时候,继续⼊队列就会阻塞,直到有其他线程从队列中取⾛元素
当队列空的时候,继续出队列也会阻塞,直到有其他线程往队列中插⼊元素.
⽣产者消费者模型
阻塞队列的⼀个典型应⽤场景就是**"⽣产者消费者模型"**
⽣产者消费者模型 有着两个重要的优势
1.解耦合
不仅存在于 两个线程中 也可以是服务器中

2.消峰填谷
将服务器的请求量看似为一个曲线图

在Java标准库中内置了阻塞队列.
如果我们需要在⼀些程序中使⽤阻塞队列,直接使⽤标准库中的即可
BlockingQueue 接⼝. 底层实现由 LinkedBlockingQueue类完成
put⽅法⽤于阻塞式的⼊队列,
take ⽤于阻塞式的出队列.
BlockingQueue 也提供了其他方法(offer,poll,peek) 但是其中不含阻塞特性.
实现
实现一个简单的生产者 消费者模型
首先自己定义一个简单的阻塞队列 MyBlockingQueue
内部要实现 put 和 take (队列 的先进先出)

定义基础变量 再完成put take 的实现
put

take

其中 notify 是put take 相互唤醒的
但是存在 多个线程put 多个线程take 可能会触发自我调用
线程池
线程池是一种多线程处理形式,通过预先创建 一组线程并管理它们的生命周期,优化系统资源的使用
本质一组线程
主要用途:高效创建 销毁 线程
为了高效创建 销毁 线程 目前保留着两种主流的方法
1.线程池
2. 协程(一种比线程更轻量级的并发编程模型)
直接创建线程 会比从线程池中取的开销会更大
简单说 直接创建线程 需要 操作系统(内核 + 配套程序)
从内核中完成的 操作系统创建线程 这一状态是 不可控 的
但是从线程池取出 线程 这一状态是 可控的
所以导致了线程池省下一些开销
标准库中的线程池
Executors 本质上是 ThreadPoolExecutor类的封装.
ThreadPoolExecutor
corePoolSize(核心线程数)
类比于 公司正式员工数量.
maximumPoolSize(最大线程数)
正式员工 + 实习生 的数量
keepAliveTime(非核心线程允许最大空闲时间)
实习生允许的 空闲**(摸鱼)**时间
unit( 是keepaliveTime 的时间单位),
workQueue(传递任务的阻塞队列 工作队列)
threadFactory(工厂模式)
和单例模式 并列的关系
给线程类提供的 工厂类
对⼀些属性进⾏了不同的初始化设置.
工厂方法 的核心是 把构造对象new 的类过程
各种属性初始化的过程 封装起来
提供 工厂方法 的类就是工厂模式
RejectedExecutionHandler(拒绝策略)
如果任务量超出公司的负荷了接下来怎么处理
一般有下列的 四种 处理方法
AbortPolicy(): 超过负荷,直接抛出异常.
CallerRunsPolicy(): 让调⽤sumbit负责处理多出来的任务.
submit,将任务加⼊线程池中
DiscardOldestPolicy(): 丢弃队列中最⽼的任务.
DiscardPolicy(): 丢弃新来的任务.
定时器
标准库中提供了⼀个Timer类
Timer类的核⼼⽅法为 schedule
定时器也是软件开发中的⼀个重要组件.
类似于⼀个"闹钟".达到指定时间之后,就执⾏某个指定好的代码
实现
MyTimerTask
Task类⽤于描述⼀个任务(作为Timer的内部类)
⾥⾯包含⼀个Runnable对象和⼀个time(毫秒时间戳)
所以这个对象需要放到 优先队列**(** 确保在多任务时 最先执行最快完成**)**中.因此需要实现 Comparable接⼝

Mytimer
Timer类提供的核⼼接⼝为schedule
⽤于注册⼀个任务,并指定这个任务多⻓时间后执⾏.
通过PriorityQueue来组织若⼲个Task对象
通过schedule来往队列中插⼊⼀个个Task对象.

Timer类 中不停的扫描队⾸元素,判断是否能执⾏这个任务

定时器除了使用有优先级队列实现 也可以使用 '时间轮'实现
优点:性能更高
缺点:时间精度不如 优先级队列