河北王校长并发编程

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队列头的线程才可以获取锁,新来的线程只能插入到队尾。

    • 等待队列数量

      • lock可以有多个等待队列
    • 公平锁支持

      • lock支持公平锁
    • 线程唤醒区别

      • wait/notify
      • newCondition.await/signal
    • 个性化定制支持

  • 线程饥饿

    • 非公平锁情况下,在同步队列等待的线程被新进入的线程抢占锁
  • 读写锁

    • state高16读,低16位写

锁升级

image-20230812223706823

  • 无锁

    • 调用原生hashcode方法,会使hashcode存放到对象头的markword,导致无法添加轻量级锁
  • 偏向锁

    • 与无锁的标志位都为01,使用偏向锁标志位区分
    • 使用线程id+epoch,存放到原先hashcode的位置(注意hashcode位置上不许存在数据),修改偏向锁标志位
    • 锁撤销(偏向锁状态竞争失败引发)
      • 偏向锁的撤销,需要等待全局安全点(在这个时间点上没有正 在执行的字节码)。
      • 首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,
      • 如果线程不处于活动状态,则将对象头设置成无锁状态;
      • 如果线程仍然活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word
        • 要么重新偏向于其他线程(偏向锁的大量撤销导致批量重偏向:20,使用epoch)
        • 要么恢复到无锁(升级为轻量级锁)
        • 要么标记对象不适合作为偏向锁(批量撤销:40)
      • 唤醒暂停的线程

    image-20230818220814093

  • 轻量级锁

    • 加锁
      • 线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录空间中,官方称为Displaced Mark Word;
      • 然后线程尝试使用 CAS将对象头中的Mark Word替换为指向锁记录的指针。
      • 如果成功,当前线程获得锁,
      • 如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋(10)来获取锁。
        • 锁升级,轻量级锁指针替换为重量级锁指针
    • 解锁
      • 会使用原子的CAS操作将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁

    image-20230818220849145

  • 重量级锁

    • 加锁
    • 解锁
      • 将锁记录空间存放数据放到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访问优化
      • 次级接口存在服务降级的情况,可以提高线程数(不推荐)
      • 添加节点,用空间换时间
    • 线程核数设置不合理的问题

      • 接口线程池设置太小,接口异常请求数量增多:
        • 如果使用抛异常降级策略,将会导致有大量的异常日志
        • 会造成接口熔断或者降级(非正常性质的)
      • 接口线程池设置太大
        • 其他接口没有足够的资源,导致系统接口积压,系统崩溃

线程

  • 线程状态转化

    • 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;
          }
      
  • 锁释放和响应中断

threadlocal

posted @ 2024-03-14 20:06  Faetbwac  阅读(10)  评论(0编辑  收藏  举报