河北王校长并发编程
volatile
- 作用
-
保障可见性(线程嗅探、MESI)
- 将当前处理器缓存行的数据写回到系统内存
- 使在其他CPU里缓存了该内存地址的数据无效
-
防止指令重排
-
在每个volatile写操作的前面插入一个StoreStore屏障。禁止volatile写之前写
-
在每个volatile写操作的后面插入一个StoreLoad屏障。禁止volatile写之后读
-
在每个volatile读操作的后面插入一个LoadLoad屏障。禁止volatile读之后读
-
在每个volatile读操作的后面插入一个LoadStore屏障。禁止volatile读之后写
-
-
特殊情况下原子性
- 单条jvm指令 new get set
-
Synchonized
重量级锁提高吞吐量
- 作用
- 同步方法块 当前实例
- 普通方法 对象
- 静态方法 class
- 实现原理
- 代码块同步是使用monitorenter 和monitorexit指令实现
- 同步方法使用ACC_SYNCHRONIZED访问标志标识
Lock
-
对比Synchonized
-
使用方式
lock unlock tryLock(非阻塞加锁) tryLock(long time, TimeUnit unit) lockInterruptibly(可中断加锁) newCondition(消息通知)
-
加锁显隐性
-
作用对象
-
非阻塞支持
- trylock方法避免用户态核心态切换消耗
-
可超时支持
- tryLock(long time, TimeUnit unit)方法避免死锁
-
可中断支持
- lockInterruptibly
-
原理
-
monitor
- 当多个线程同时访问同步代码块时,首先通过CAS的方式尝试将Monitor中的owner字段设置为当前线程,同时count加1,若发现之前的owner的值就是指向当前线程的,recursions也需要加1。如果CAS尝试获取锁失败,则进入到EntryList中。
- 当前线程执行完同步代码块时,则会释放锁,count减1,recursions减1。当recursions的值为0时,说明线程已经释放了锁。
- 当获取锁的线程调用
wait()
方法,则会将owner设置为null,同时count减1,recursions减1,当前线程加入到WaitSet中,等待被唤醒。
-
AQS
- ReentrantLock先通过CAS尝试获取锁
-
如果此时锁已经被占用,通过CAS该线程加入AQS队列尾部并等待
-
当前驱线程的锁被释放,挂在CLH队列为首的线程就会被唤醒,然后继续CAS尝试获取锁,此时:
-
非公平锁,如果有其他线程尝试lock(),有可能被其他刚好申请锁的线程抢占。
-
公平锁,只有在CLH队列头的线程才可以获取锁,新来的线程只能插入到队尾。
-
-
- ReentrantLock先通过CAS尝试获取锁
-
-
等待队列数量
- lock可以有多个等待队列
-
公平锁支持
- lock支持公平锁
-
线程唤醒区别
- wait/notify
- newCondition.await/signal
-
个性化定制支持
-
-
线程饥饿
- 非公平锁情况下,在同步队列等待的线程被新进入的线程抢占锁
-
读写锁
- state高16读,低16位写
锁升级
-
无锁
- 调用原生hashcode方法,会使hashcode存放到对象头的markword,导致无法添加轻量级锁
-
- 与无锁的标志位都为01,使用偏向锁标志位区分
- 使用线程id+epoch,存放到原先hashcode的位置(注意hashcode位置上不许存在数据),修改偏向锁标志位
- 锁撤销(偏向锁状态竞争失败引发)
- 偏向锁的撤销,需要等待全局安全点(在这个时间点上没有正 在执行的字节码)。
- 首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,
- 如果线程不处于活动状态,则将对象头设置成无锁状态;
- 如果线程仍然活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word
- 要么重新偏向于其他线程(偏向锁的大量撤销导致批量重偏向:20,使用epoch)
- 要么恢复到无锁(升级为轻量级锁)
- 要么标记对象不适合作为偏向锁(批量撤销:40)
- 唤醒暂停的线程
-
轻量级锁
- 加锁
- 线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录空间中,官方称为Displaced Mark Word;
- 然后线程尝试使用 CAS将对象头中的Mark Word替换为指向锁记录的指针。
- 如果成功,当前线程获得锁,
- 如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋(10)来获取锁。
- 锁升级,轻量级锁指针替换为重量级锁指针
- 解锁
- 会使用原子的CAS操作将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁
- 加锁
-
重量级锁
- 加锁
- 解锁
- 将锁记录空间存放数据放到monitor的header,将owner设置为自己
双重检查锁
public class Singleton {
//volatile 解决指令重排导致的npe,在new指令前后添加storestore以避免
private volatile static Singleton singleton;
private Singleton() {
}
//DLC 双重校验加锁 懒汉式单例
public static Singleton getInstance() {
if (singleton == null) {
// 避免线程指针不为空的情况下,再次进行锁的争夺而阻塞,造成用户态到内核态的的转换消耗
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
/*
初始化操作并非原子命令,可能指令重排导致npe异常
分配内存空间
初始化对象
指向分配好的内存空间
*/
}
}
return singleton;
}
}
线程池
-
当前程序可以使用当前机器的所有资源
- 基于先辈程序员的理论基础和实践,进行线程池的参数合理调整。
- cpu密集型 n+1
- io密集型 2n
- 基于先辈程序员的理论基础和实践,进行线程池的参数合理调整。
-
当前程序不可以使用机器的所有资源
-
由于我们目前机器不是单机部署的,而是有服务集群部署。具体计算
-
案例 2 : 3 : 1 : 1 : 1
- 两个核心服务接口(承载62.5%)
- 第一个接口访问比例 1/4 因此分配1/4的线程资源
- 第二个接口访问比例 3/8 因此分配 3/8 的线程资源
- 三个可降级接口(承载37.5%)
- 第三个接口访问比例 1/8 因此分配 1/8 的线程资源
- 第四个接口访问比例 1/8 因此分配 1/8 的线程资源
- 第五个接口访问比例 1/8 因此分配 1/8 的线程资源
-
最大线程数:对应比例的线程资源,考虑CPU核数,其他接口的最大QPS和本机活跃的live的线程数。
-
\[最佳线程数 = \frac {RT}{CPU Time} * CPU 核数 * CPU利用率 \]
-
\[CPU Time = \frac {1000ms}{QPS} * CPU 核数 * CPU利用率 \]
-
-
核心线程数:
- 初次压测设置成为一样大,测试最大并发访问的情况。
- 撑得住,调小核心线程数。
- 撑不住,调整queue
- 初次压测设置成为一样大,测试最大并发访问的情况。
-
等待队列长度
-
不推荐无界队列:干掉了最大线程数参数,无限积压请求导致OOM问题
-
推荐有界队列:
-
模拟高峰访问时段的压测,看最大等待数量,不推荐使用 Integer.max_value
-
\[等待队列大小 = (一小时的访问数量 - 小时接口能处理的请求数量) * 150 \% \]
-
-
-
饱和策略:
- 按照具体情况具体分析
- 金融的话,可以直接抛弃
- 重试机制
- 日志记录,持久化机制策略
- 按照具体情况具体分析
-
未达到预期
- 已经达到最大线程数3/8,调大队列的最大等待数
- 不影响其他接口;当前线程只是等待,而不是不可用
- 优化接口代码,提高并发性
- 服务拆分,接口异步化
- 缓存优化
- DB访问优化
- 次级接口存在服务降级的情况,可以提高线程数(不推荐)
- 添加节点,用空间换时间
- 已经达到最大线程数3/8,调大队列的最大等待数
-
线程核数设置不合理的问题
- 接口线程池设置太小,接口异常请求数量增多:
- 如果使用抛异常降级策略,将会导致有大量的异常日志
- 会造成接口熔断或者降级(非正常性质的)
- 接口线程池设置太大
- 其他接口没有足够的资源,导致系统接口积压,系统崩溃
- 接口线程池设置太小,接口异常请求数量增多:
-
线程
-
线程状态转化
-
NEW 状态:
- Thread thread = new Thread();
-
RUNNABLE 状态:
- thread.start();
-
WAITING 状态:
- thread.wait();
- thread.park();
- thread.join();
-
TIME_WAITING 状态:
- thread.wait(100);
- thread.park(100);
-
BLOCKED 状态:
- synchronized 进入失败
-
TERMINATED 状态:
- run() 方法执行结束
-
-
源码
-
public enum State { /** * 尚未启动的线程的线程状态。 */ NEW, /** * 可运行线程的线程状态。可运行程序中的线程 * 状态正在Java虚拟机中执行,但它可能 * 正在等待来自操作系统的其他资源 * 例如处理器。 */ RUNNABLE, /** * 被阻止等待监视器锁定的线程的线程状态。 * 处于阻塞状态的线程正在等待监视器锁定 * 输入同步块/方法,或调用后重新输入同步的块/方法 *{@link Object#wait()Object.wait}。 */ BLOCKED, /** * 等待线程的线程状态。 * 线程由于调用以下方法: * <ul> * <li>{@link Object#wait()Object.wait}没有超时</li> * <li>{@link #join()Thread.join}没有超时</li> * <li>{@link LockSupport#park()LockSupport.park}</li> * </ul> * * <p>处于等待状态的线程正在等待另一个线程执行特定动作。 */ WAITING, /** * 具有指定等待时间的等待线程的线程状态。 * 线程由于调用以下线程之一而处于定时等待状态 * 具有指定正等待时间的以下方法: * <ul> * <li>{@link#sleep线程.sleep}</li> * <li>{@link Object#wait(long)Object.wait}超时</li> * <li>{@link#join(long)Thread.join}超时</li> * <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li> * <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li> * </ul> */ TIMED_WAITING, /** * 已终止线程的线程状态。 * 线程已完成执行。 */ TERMINATED; }
-
-
锁释放和响应中断