学习笔记9——JUC三种量级的锁机制

2023-12-13 06:21:59

学习笔记系列开头惯例发布一些寻亲消息

链接:https://baobeihuijia.com/bbhj/contents/3/197325.html
在这里插入图片描述

  • 多线程访问共享资源冲突

    • 临界区:一段代码块存在对共享资源的多线程读写操作,称这段代码块为临界区

    • 竞态条件:多个线程在临界区内执行,由于代码的执行序列不同导致结果无法预测,称发生了竞态条件

  • 一些线程安全的例子(什么时候加锁什么时候不加):

    • 局部变量是线程安全的(每个线程创建的栈内部独立拥有)
      在这里插入图片描述

    • 局部变量引用的对象:如果用的对象的公共变量,需要加锁,因为对象在堆内
      在这里插入图片描述

    • 局部变量引用的对象:如果用的是对象方法内的局部变量**(虽然这种情况下每个线程在堆内单独拥有对象使用权,但是如果子类重写方法,新加了线程,那么也会出现两个线程共享局部变量的情况,所以尽量用final或者private将父类保护起来)**

    在这里插入图片描述

    • 线程安全类:

      • string:在定义string类的时候,就已经声明了final,所以不会出现子类继承string,并且修改string的读写逻辑为多线程,所以对于单独一个string来说,我们的读写是安全的,因为只有有一个线程进行读写,但是多个方法的组合之间可以插入不同的别的线程,所以不是原子或者说不是安全的。
        在这里插入图片描述
  • 在这里插入图片描述

  • 重量级锁——synchronized【父母考核:我听父母的,父母来决定我的owner,当追求者很多时,采用这种方案】

    • 加锁room,别的线程会进入block,直到本线程释放锁,才会唤醒别的block线程,在这个过程中就算时间片轮完该线程,其他线程也无法唤醒

    • 用对象锁的形式保证了临界区代码的原子性,避免多个线程一起执行同一段临界区代码,如果这个锁没有被用,那么就可以执行这段代码

    • 锁住对象和锁住类对象是不同的,并不互斥(也就是说,在执行锁住对象的时候,当这轮cpu片时间耗尽,cpu也会去执行锁住类对象的操作,类对象锁不会被阻塞)

      # 方法一
      synchronized(对象)
      
      # 方法二
      class Room {
          private int counter = 0;
          
      	// 等价于 synchronized(this)
          public synchronized void increment() {
              counter++;
          }
      
          public synchronized void decrement() {
              counter--;
          }
      
          public synchronized int getCounter() {
              return counter;
          }
      }
      
      # 方法三
      class Test{
      	// 等价于synchronized(Test.class)
      	public synchronized static void test(){
      	}
      }
      
    • Monitor

      • 对象头

        • 普通对象:32bit的Mark Word(25位hashcode,每个对象都有自己的哈希码,4位的GC分代,锁状态标志)+32bit的Klass Word(类型指针)
      • 数组对象:32bit mark word + 32bit klass word + 32bit的 数组长度

      外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    • synchronized的原理:

      • 每次线程遇到synchronized(obj),会去检查obj是否指向操作系统中的一个monitor,如果没有指向那就将obj对象的mask word 修改为monitor的地址,并将锁标志位从01改为10,将该线程置为monitor的Owner
      • 在此期间如果别的线程执行遇到synchronized(obj),那么就会索引到monitor,发现已经有别的owner,进入entrylist 变为BLOCKED状态
      • 当前owner执行完成后,随机唤醒一个BLOCKED线程,最后退出该对象时将自己的markword重置为hashcode等

      外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    • 重量级锁的线程自旋优化

      • 为了避免线程阻塞,发生上下文切换,从而自旋等待解锁
      • 适合多核cpu,单核的cpu没有自旋的意义,因为没有额外的cpu可以来执行自旋(需要等你结束我才能执行,但是我还总要中途打扰你问你好了没)
      • 自旋失败:几次自旋重试后还是得不到锁就进行阻塞
  • 轻量级锁(操作系统自动创建,不需要我们显式定义)【私定终生,我来决定我的owner】

    • 加锁:线程中创建一个LOCK RECORD 00表示轻量级锁,与对象头交换mask信息(类似于一种密码机制,只有当前线程完成后,才会把密码信息还给对象,别人来访问交换时这个对象已经是加锁状态)

    • 锁重入:当前线程已经拿到锁了,但是又执行了一遍synchronized(obj),这时会新建一个LOCK RECORD 00栈帧,数据这里会存储null,说明其他栈帧已经拿到锁了

    • 解锁:如果是null,直接清除即可,不是null则需要将mask word恢复给对象

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    • 锁膨胀(追求者很多产生了竞争,发现决定权给到父母后,就需要与父母沟通)

      • 线程加轻量级锁失败,进入锁膨胀流程,变成重量级锁(修改原始密码为monitor地址,owner置为当前线程,后续线程进入阻塞)
      • 原来线程的轻量级锁解锁失败(因为密码匹配不对,对象的密码现在是monitor地址),需要进入重量级锁的解锁流程,即找到monitor对象,将owner置为null,并唤醒阻塞线程

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 偏向锁(只有一个线程一直重复用偏向——》有别的线程也来用该对象就会撤销变为轻量级锁——》锁之间有竞争交互就用重量级)

    • 原始:将对象的mask word与新建锁栈帧的头部信息交换(锁重入都会尝试交换信息,造成资源浪费)

    • 偏向:将对象的mask word修改为线程ID,锁重入就不会新建锁栈帧

    • 当前线程id释放后,线程id还是不会改变,该对象已经从属于该主线程

    • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    • ban掉偏向锁的方法

      • 当一个可偏向的对象调用了hashcode就会ban掉偏向锁
      • 当对象已经偏向一个线程了,另一个线程也来用该对象,但是不竞争,就会变为轻量级(线程id部分变为锁记录指针),如果竞争交互就是重量级
      • 调用wait,notify也会撤销,因为wait,notify只有重量级有
    • 批量重偏向

      • 超过一定阈值数量(20)的对象都在撤销偏向锁,就会认为偏向设置的线程有误,因此会将所有的对象偏向锁改为别的线程
      • 撤销的性能消耗比较大
      • 批量执行
    • 批量撤销

      • 撤销超过阈值(40)的对象都在撤销偏向锁换轻量级(说的是所有线程的总数),偏向就要求整个类原始对象和新对象不再偏向,直接轻量级
  • JIT即时编译进行逃逸分析

  • sleep和wait的区别(状态都是timewaiting)

    • sleep是线程的方法,执行时不会放弃锁:相当于屋子内的人把门锁死,直到醒来检查一下资源是否满足

    • wait是对象A的方法,会放弃锁,需要notify叫醒:屋子内的人发现缺资源就出来在门外等着A.wait(),等到资源满足执行notify【这种方法可以通过资源的识别需要通过while条件来判断】

      • synchronized(lock){
        	while(资源不满足){
        		lock.wait();
        	}
        	// 干活
        }
        
        synchronized(lock){
        	lock.notifyAll()
        }
        

文章来源:https://blog.csdn.net/CZY925323/article/details/134882648
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。