CH01
- 并发:多个线程操作相同的资源,保证线程安全,合理使用资源
- 高并发:服务能同时处理很多请求,提高程序性能
CH02
- CPU中有多级缓存(CPU远比主存快),可保证是时间局部性和空间局部性
- 多级缓存的缓存一致性(MESI):四个状态
- Modified: 被修改过的,需要在未来某个时刻重新写回主存,在这之间允许其他CPU读取
- Exclusive: 未被修改的,与主存状态一致
- Shared: 共享状态,可能被多个CPU进行缓存,与主存数据一致
- Invalid: 当有CPU修改该缓存行时,进入此无效态
- 多级缓存的乱序优化: 处理器为提高运算速度而做出违背代码原有顺序的优化
- JMM: 每个线程都拥有数据的私有拷贝
- JMM同步八种操作:
- 锁定: 作用于主内存的变量,标示为线程独占
- 解锁: 作用于主内存的变量
- 读取: 作用于主内存,从主内存输送到工作内存
- 载入: 作用于工作内存, 放入到变量副本中,与读取必定顺序
- 使用: 输送给执行引擎
- 赋值: 从执行引擎到工作内存
- 存储: 工作内存到主内存
- 写入: 工作内存写入主内存,与存储必定顺序
CH03
- 并发模拟工具:
- Postman: Http请求模拟工具。在Collection的run中测试
- Apache Bench: 测试网站性能, terminal工具。用法: ab -n 1000 -c 50 http://localhost:8080/test
- JMeter(首选): Apache组织开发的压力测试工具
- 代码: Semaphore
CH04
- 线程安全性:
- 原子性: 互斥访问. e.g. Atomic包
- 可见性: 一个线程对主内存的修改可以及时的被其他线程看到
- 有序性: 一个线程观察其他线程中的指令执行顺序, 由于指令重排, 该观察结果一般杂乱无序
- Atomic里面unsafe类, 提供了
getAndAddInt()
方法, 具体实现使用了compareAndSwapLong()方法, 是Java的native方法. 整个getAndAddInt()方法的关键是不停判断从主存读回来的值和当前主存的值相同, 才写入新的值(CAS操作), 否则循环. 当CAS操作失败几率高时, 性能会下降 - Atomic里面的LongAdder比AtomicLong高效, 思想是把64位int转成Cell数组, 降低对value更新的并发数,也就是将对单一value的变更压力分散到多个value值上,降低单个value的“热度”.
- 锁:
- synchronized: 以来JVM
- Lock: 依赖特殊的CPU指令
- synchronized:
- 修饰代码块: 作用于调用的对象, 实验结果是不同实例调用该方法是非同步的
- 修饰方法: 作用于调用的对象, 实验结果是不同实例调用该方法是非同步的
- 修饰静态方法: 作用于所有对象, 实验结果是不同实例调用该方法是同步的
- 修饰类: 括号括起来的部分, 作用于所有对象, 实验结果是不同实例调用该方法是同步的
- synchronized是不能继承的, 子类想要有必须自己显式写明
- 可见性:
- 不可见的原因(结合JMM模型理解):
- 线程交叉执行
- 重排序结合线程交叉执行
- 共享变量更新后的值没有在工作内存与主存及时更新
- synchronized的可见性:
- 线程解锁前, 必须把共享变量的最新值刷新到主内存
- 线程加锁时, 将清空工作内存中共享变量的值, 从而使用共享变量时需要从主内存中重新读取最新的值
- PS: 这里说的锁是同一把锁, 因此不同类之间不会相互影响
- volatile保证可见性
- 加入内存屏障和禁止重排序
- volatile的读写操作直接与主存相关(但还是会有工作内存, 只是修改完直接刷新)
- PS: 更适合用来做判断标记量
- 不可见的原因(结合JMM模型理解):
- 有序性: happens-bofore原则
- 含义: 指定某些规则来规定某个线程修改的变量何时对其他线程可见
- 作用: 在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系。