共享模型之管程
共享带来的问题
上下文切换问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| + 共享带来的问题 + 上下文切换分析 + 单线程 + 多线程情况 + 线程的 cpu 时间片用完 -> 线程挂起,运行到就绪 -> 线程上下文切换 -> 字节码指令交错运行 -> 共享变量counter读写冲突 + 根源:指令交错 + 临界区与竞态条件 + 临界区 + 存在对共享资源的多线程读写区域 + 竞态条件 + 结果无法预测 + 避免竞态条件 + 非阻塞式 + 原子变量 + 阻塞式 + synchronize、lock + synchronize + 对象锁 + 临界区代码变成串行 + 对象,多个线程共享 + 理解 + 比喻 + synchronize中即使时间片用完,下次获取时间片仍进入(上下文切换) + 只有执行完其中代码,从synchronize出来解锁 + 时序图 + 执行中,若本线程上下文切换 + 其他线程获取锁被阻塞blocked + 其他线程直接上下文切换,本线程继续执行 + 释放锁,唤醒阻塞线程 + 思考 + 利用对象锁保证临界区中代码的原子性 + 原子性:原子操作,不可分割 + 面向对象改进 + 互斥等逻辑封装在room类内部 + 对共享资源保护,由内部实现 + 对外调用即可 + synchronize加在方法上 + 成员方法上 + 相当于锁this对象 + static方法上 + 相当于锁类对象 + jvm中只有一个类对象,但是有多个实例对象。这就是区别 + 习题:线程八锁 + sleep即抱着锁睡觉 + 锁类对象只有一个
|
线程安全分析
1 2 3 4 5 6 7 8 9 10 11
| + 变量的线程安全分析 + 成员变量、静态变量被共享 + 读操作,线程安全 + 读写操作,临界区,考虑线程安全 + 局部变量 + 局部变量在线程之内,不共享 + 线程安全 + 局部变量引用的对象,可能被共享 + 如果引用对象逃离方法的作用范围,考虑线程安全 + 暴露引用 + private、final,防止子类影响
|
常见线程安全类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| + 线程安全类 + 多个线程调用他们同一实例时是安全的 + hashtable put + 多个方法组合不安全:非原子 + 还需在外部上锁 + 不可变类 + 直接创建新的实例,不在原有实例上改变 + 线程安全 + 实例分析 + final:对象的引用不可变不代表对象的状态不可变 + 还可以使用ThreadLocal给每个线程存储一个私有start变量 + 我觉得主要还是看变量是否1、能逃离,2、共享 + 先看有木有多个线程同时访问共享变量,再看该共享变量是否可被修改 + 逃逸分析,要符合闭合原则 + 习题 + 售票 + sell读写操作,不安全 + add线程安全,有synchronize + sell和add + 安全, 不是同一共享变量 + 概括来说就是:下边的原子操作不依赖上边原子操作的结果的话,就不用考虑两个原子操作合在一起的安全性 + threadlist安全,不被多个线程共享 + sell加锁 + 保证原子性 + 转账 + 加锁:类对象
|
Monitor
加锁实现原理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| + 对象头 + klass word + 找到的类对象 + mark word + monitor(锁) + 加锁实现原理 + 对象与monitor相关联 + 每个synchronize(obj)对象关联一个monitor + 只能有一个owner + 其他线程执行synchronize(obj) + 进入entrylist blocked + 执行完同步代码块内容 + 唤醒entrylist中等待线程 + 线程相互竞争owner + 原理(字节码) + 如果出现异常,帮我们正确解锁 + 重置mark word,唤醒entrylist + 抛出异常
|
synchronize优化原理
对象头格式:正常、偏向锁、轻量级锁、重量级锁、GC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
| + synchronize优化原理 + 小故事 + 轻量级锁 + 创建锁记录(Lock Record)对象 + 线程栈帧中包含锁的结构 + 锁记录中object reference指向锁对象 + 用cas替换lock record和锁的mark word + 用于解锁的恢复和锁记录地址 + cas替换成功 + 锁中存储了锁记录地址(哪个线程)和轻量级锁 + 表示该线程给对象加锁 + cas替换失败 + 如果其他线程持有了该obj的轻量级锁 + 进入锁膨胀过程 + 如果自己synchronize锁重入 + 添加lock record,知道是自己线程的锁,作为重入的计数 + 解锁时如果取值为null,表示锁重入,清除锁记录 + 解锁时如果取值不为null + cas将mark word恢复给锁对象头 + 变为无锁状态 + 成功,解锁成功 + 失败 + 说明轻量级锁进入了锁膨胀或一级升级为重量级锁 + 进入重量级锁解锁流程 + 锁膨胀 + 将轻量级锁升级为重量级锁 + 流程 + 为object对象申请monitor锁 + mark word指向重量级锁地址,后两位10 + 其他线程进入monitor的entrylist阻塞 + 线程执行完,解锁 + cas将mark word恢复,失败 + 进入重量级解锁流程 + 根据对象头找到monitor + 设置owner为null + 唤醒entrylist + 自旋优化 + 重量级锁竞争时,自旋优化(重试) + 减少阻塞发生 + 阻塞要进行上下文切换 + 自旋失败,进入阻塞 + 自旋锁是自适应的,正反馈调节 + java7以后无法控制是否开启自旋 + 偏向锁 + 轻量级锁,锁重入时仍需CAS操作 + 每一次都要cas操作(翻书包) + 偏向锁 (刻名字) + 第一次cas时将Thread id设置到锁对象头 + 重入时判断为本线程,无需cas + 偏向状态 + 默认开启偏向锁,mark word中biased_lock为1 + 默认延迟开启 + 获取锁对象 + 设置线程id到锁的mark word + synchronize结束后 + 锁的mark word不变 + 其他线程获取锁对象 + 撤销偏向锁,变成轻量级锁 + 竞争 + 膨胀变成重量级锁 + 使用偏向锁之前获取hashcode + 会禁用偏向锁 + 哈希码用的时候才产生,填到mark word中 + 轻量级锁hashcode存在线程栈帧的锁记录里 + 重量级锁hashcode存在monitor对象里 + 解锁时都会还原回来 + 偏向锁没有存储的地方 + 撤销 + 调用hashcode + 其他线程使用对象 + 偏向锁升级为轻量级锁 + 测试 + 轻量级锁、偏向锁前提 + 线程错开,否则升级重量级锁 + wait会释放CPU和锁资源 + 多个线程获取偏向锁 + 撤销偏向锁 + 调用wait/notify + wait/notify只有重量级锁有 + 自动升级为重量级锁 + 批量重偏向 + 虽然被多个线程访问,没有竞争 + 让偏向锁重新偏向,而不升级轻量级锁 + 撤销偏向次数过多:超过20次 + 重新偏向新线程 + 总结就是没超过阈值,先把偏向锁变成轻量锁,然后再偏向锁 + 批量撤销 + 撤销偏向次数过多:超过40次 + 整个类所有对象不可偏向 + 测试 + t1 + 39个偏向t1(偏向锁) +t2 + 前19个撤销,变成轻量级锁,解锁不可偏向,(轻量级锁) + 19以后批量重偏向优化,重新偏向于t2,(偏向锁) + t3 + 前19个撤销,不可偏向 + t3前19个已被t2设为不可偏向,为轻量级锁 + t3前十九个已经被t2设置为不可偏向锁,故t3初始为不可偏向锁,加锁成为轻量级锁 + T2前十九个没有重偏向,解锁后就变为不可偏向,后二十是偏向t2 + 19以后, + 原本偏向t2线程 + 被撤销,升级为轻量级锁 + 解锁之后不可偏向 + 重偏向是针对单个线程来讲的,一个线程撤销20个锁才进行重偏向,这时另一个线程还是要撤销20个 + t1:全部偏向t1;t2:一半撤销t1的偏向锁,一半偏向t2;t3:一半轻量级锁,一半撤销t2的偏向锁;总共撤销了20次t1的偏向锁,20次t2的偏向锁 + 批量重偏向和批量撤销是针对类的优化,和对象无关。偏向锁重偏向一次之后不可再次重偏向。当某个类已经触发批量撤销机制后,JVM会默认当前类产生了严重的问题,剥夺了该类的新实例对象使用偏向锁的权利 + 锁消除 + JIT优化 + 分析局部变量,synchronize中不会被共享 + 把synchronize优化掉
|
wait、notify
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| + wait、notify + 为什么需要wait + 进入休息室wait + 烟到了notify,唤醒wait线程 + 工作原理 + owner线程发现条件不满足,放弃锁 + 调用wait方法进入waitset + WAITING:没有时限 + blocked和waiting都处于阻塞 + 不占用cpu时间片 + owner调用notify或者notifyall唤醒 + 进入entrylist重新竞争 + api + 都需要获得锁成为owner才能调用 + 同步代码块中 + notify挑一个唤醒 + 有参wait + 最大时限等待 + sleep 和 wait + sleep是thread方法,wait是object所有对象方法 + sleep不需要和synchronize使用,wait需要获取锁 + sleep睡眠不会释放锁,wait等待时会释放锁 + wait和notify使用 + wait时其他线程可以继续工作 + notify错误唤醒:虚假唤醒 + notifyall全部唤醒 + 指定的话,用park,unpark + notifyall + while + if条件改为while, + 只有条件正确才能唤醒出循环 + 正确使用
|
设计模式
保护性暂停
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| + 设计模式:同步模式之保护性暂停 + 定义 + 一个线程等待另一个线程执行结果 + 一个结果从一个线程传递到另一个线程,关联同一个GuardedObject + 结果不断传输,需要消息队列(生产者、消费者) + join、future实现,采用保护性暂停 + 等待结果,同步模式 + 实现 + guardedobject + 优点 + 相比join,不需要等线程结束,线程还可以继续运行 + 等待结果变量无需设置全局,直接获取 + 拓展-增加超时 + wait time + 测试 + join原理 + 记录经历时间,等待 + 保护性暂停 + 拓展-解耦等待和生产 + 解耦,生产者和消费者 + 维护一个集合,guardedobject有唯一id + 实现 + mailbox + 线程安全的map hashtable + 自增id方法加锁 + 获取完邮件直接删除,所以remove + postman、people + 继承Thread + people + postman + 测试 + //确保生成居民先于快递员,但是快递员发信要早于居民收信 + 解耦了生产者和消费者 + 生产者和消费者必须一一对应
|
生产者消费者
生产者消费者-消息队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| + 异步模式之生产者消费者 + 定义 + 不需要生产者和消费者一一对应 + 消息队列 + 有容量限制 + 各种阻塞队列,就是这种模式 + 异步模式 + 等待通知 同步,等待需要 通知 之后的结果 + 实现 + message + messageQueue + LinkedList 双向队列 + 获取消息 + 队列为空wait等待 + 不为空,取出队列头部消息 this的话锁粒度太大了,直接就相当于单线程操作了…… + 取完消息,notify唤醒生产者,队列不满 + 存入消息 + 判断队列否已满 + 生产者wait等待 + 尾部加入消息 + notify唤醒消费者,接收消息 + 测试 + 生产者 + 消费者
|
Park&unpark
先调用unpark原理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| + park&unpark + 基本使用 + 暂停当前线程 + wait状态,无时限等待 + 若先调用unpark + 无法park住 + 特点 + 无需配置monitor + 以线程为单位阻塞、唤醒线程,精准唤醒 + 可以先unpark + 原理 + 每个线程都有parker对象(背包) + counter(0、1) + 干粮 + cond + 帐篷 + mutex + unpark:令干粮充足 + 调用park + 调用unpark + 先调用unpark + 说白了就是当count为0时调用park才会停 + 总结 + park消费counter + 若无counter进入cond,counter为0 + 若unpark,counter为1 + 唤醒cond中线程,消费counter为0,继续运行 + 若有counter,counter为0,继续运行
|
线程状态转换
线程状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| + 线程状态转换
+ new + 创建java对象,还没和操作系统线程对象关联 + runnable + 关联操作系统线程,cpu调度执行 + 包括是否被cpu调度 + 调用操作系统阻塞io相关的api,java层面都是runnable + runnable和waiting + 使用synchronize获取对象锁后,wait(),进入waitset + notify、interrupt,进入entryset() + 竞争锁成功,runnable + 竞争锁失败,blocked + 调用t线程的join(),当前线程变成waiting + t线程结束、当前线程interrupt,变成runnable + park + unpark、interrupt + runnable和timed_waiting(有时限waiting) + 获取了synchronize对象锁之后,wait(n) + 等待超时、notify、interrupt,进入entryset + 竞争锁成功,runnable + 竞争锁失败,blocked + join(n) + 等待超时、运行结束、interrupt,runnable + parkNacos(n)、parkUntil(n) + 超时、unpark、interrupt,runnable + sleep(n) + 超时,runnable + runnable和blocked + 竞争锁失败,blocked + 竞争锁成功,runnable + terminated + 当前线程所有代码执行完毕
|
多把锁
JUC-可重入锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| + 多把锁 + 一把锁,并发度低 + 粒度细分,准备多个对象锁 + 业务不能有关联 + 增加并发度 + 可能造成死锁 + 活跃性 + 死锁现象 + 一个线程需要同时获取多把锁 + 都在等待对方释放锁 + 定位死锁 + jconsole、jstack + 哲学家就餐问题 + 活锁 + 两个线程互相改变,无法达到结束条件 + 没有阻塞 + 执行时间交错开,随机睡眠时间 + 饥饿 + 线程优先级太低,始终无法得到cpu调度 + 顺序加锁的解决方案,都按A、B顺序加锁 + 产生饥饿问题 + 在争夺左筷子(最外层锁)中,苏和阿同时争夺c1,所以概率倒数,在 第二轮争夺右筷子时,c5没有人跟赫争,故赫能吃到饭的概率最高 + ReentrantLock 可重入锁 + 特点 + 可中断(破坏锁) + 可设置超时时间(争抢锁) + 可设置为公平锁(block先进先出) + 支持多个条件变量(不同条件对应多个waitset,精准唤醒) + 与synchronize一样,支持可重入(同一个线程可以重复获取锁) + 可重入 + 同一个线程获取锁后,可再次获取锁(lock多次) + 可打断 + 等待锁过程中,可以被interrupt打断终止等待 + lockInterruptibly方法获取锁(lock无法打断等待) + 其他线程interrupt()打断 + 捕捉到InterruptException异常 + 直接返回 + 被动,避免死等 + 锁超时 + 主动,设置超时避免死等 + trylock(),尝试获得锁,返回布尔值 + 获取不到锁直接返回 + 返回为真,获得到了锁,向下执行代码 + trylock()带参数 + 等待获取锁时间,超时返回false + 可被打断,进入打断异常,直接返回 + 解决哲学家就餐 + 筷子继承可重入锁 + trylock() + 拿不到锁就结束,释放所有锁 + 左手拿不到继续获取,右手拿不到,释放左手锁 + 公平锁 + 默认不公平 + 公平锁:先入先得锁 + 为了解决饥饿问题 + trylock也可以解决 + 没有必要,降低并发度 + 非公平锁:没进入等待队列也有机会获得锁 + 条件变量 + 支持多个条件变量(休息室),可以精准唤醒 + 操作 + 创建条件变量(休息室) + 指定条件变量执行await,等待 + 时间参数 + 指定条件变量执行signal,唤醒 + 唤醒之后重新竞争lock锁 + 使用 + 不看源码,自己把源码敲了一遍,的确有些自己一直没注意到的地方
|
设计模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| + 同步模式之顺序控制(控制线程的运行顺序) + 固定运行顺序(先执行2,后执行1) + wait¬ify + while条件判断 + wait、notify + await&signal + join() + park&unpark + 精准暂停,唤醒 + 背包干粮 + 若t2先运行,t1可正常继续执行 + 交替输出(按abc顺序交替输出) + wait¬ify + 等待标记:用整数1、2、3标记条件,代表某个数字允许打印 + 面向对象code,很强 + await&signal + 三个不同休息室(条件变量) + 进入当前休息室 + 打印 + 唤醒下一间休息室 + 执行流程 + 不管谁抢到锁,都进入休息室 + 只有a休息室解锁,a打印,解锁b休息室 + 只有b会继续运行,打印,解锁c + 之前已经说过了,可以先唤醒,后等待,不存在全部等待的问题 + park&unpark + 以线程为单位停止、恢复 + 我一直以为我很懂面向对象,写的太烂了 + 面向对象编程,确实牛逼 + 流程 + 无论谁抢到时间片,全部线程park + 主线程唤醒t1,t1线程打印,唤醒t2 + t2打印,唤醒t3
|
小结
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| + 知识点 + 共享资源、临界区 + synchronize + 保证临界区代码线程安全 + 原子性 + 不至于上下文切换产生交错 + 锁对象语法 + 成员、静态方法 + wait、notify同步方法 + 条件不满足等待,条件满足恢复运行 + reentrantlock + 可打断、锁超时trylock、公平锁、条件变量 + 分析变量线程安全性,常见线程安全类 + 线程活跃性 + 死锁、活锁、饥饿 + 应用 + 互斥 + synchronize和lock共享资源互斥 + 同步 + wait/notify、await/signal线程间通信效果 + 原理 + monitor、synchronize、wait/notify + synchronize + 轻量级锁、偏向锁、重量级锁 + 锁膨胀、锁消除等 + park&unpark + synchronize实现monitor,JVM层面,c++ + lock实现monitor,java级别 + 设计模式 + 同步模式之保护性暂停 + 线程之间获得结果,一一对应 + 异步模式之生产者消费者 + 生产消费不是一一对应关系 + 同步模式之顺序控制 + 控制线程执行先后次序、交替运行
|