Java多线程并发之锁的概念/同步关键字/synchronized在JVM的底层是如何实现的

系列目录

今天又是爱学习的一天哦~html

日期:2019-6-27 21:10:45java

锁的概念

自旋锁

  • 概念:不放弃CPU事件,不断使用 CAS 尝试对数据进行更新,直到成功
  • 实例:AtomicInt 使用自旋锁,保证数据的原子性
  • 其实也是一种乐观锁

乐观锁

悲观锁

独享锁(写锁)

共享锁(读锁)

可重入锁

  • synchronized 就是可重入锁
    • 同一个线程,在拿到一次锁以后,能够继续调用同一把锁所同步的代码。
public synchronized void task(Object arg){
	System.out.print(Thread.currentThread() + "开始执行" + arg);
    if(arg == null){
     	this.task(new Object());   
    }
    System.out.print(Thread.currentThread() + "执行结束" + arg);
}

//main
new Obj().task(null);
复制代码

同步关键字

synchronized

线程封闭

锁消除

  • 对于某些局部变量的代码,可能不会出现线程安全问题,那样锁就会被消除
public void genStr(){
    //JIT 优化,消除了锁
    StringBuffer sb = new StringBuffer();
    sb.append("a");
    sb.append("b");
    sb.append("c");
}
复制代码

锁粗化

属于运行时的 JIT 编译优化git

  • 锁的范围会被扩大
  • sych 会锁到 for 循环外面
public class LockDemo01{
    int count;
  	public void runTest(){
     	 for(int i = 0; i < 10000; i++){
          	synchronized(this){
             	count++;   
            }
         }
    }
}
复制代码

经过 JitWatch 查看通过 jit 优化后的代码

www.cnblogs.com/stevenczp/p… www.cnblogs.com/stevenczp/p…github

一、 输出jit日志 (windows)在jre/bin/server  放置hsdis动态连接库 eclise、idea等工具,加上JVM参数 -server -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:+LogCompilation -XX:LogFile=jit.logwindows

二、 工具安装 下载  github.com/AdoptOpenJD… 解压 经过maven运行 mvn clean compile exec:java安全

三、 配置jitwatch 页面选择 config, 配置要调试的项目src源码路径,和class编译路径 打开jit.log 点击start 四、 在分析的结果中,选中指定的类,再选择右侧的具体方法,则弹出jit编译结果app

同步关键字加锁原理

大致结构

Obj Head 中的 mark word

BitFields 和 State 的对应关系。maven

  • Lock Record Address 存储 thread stack 中 LockRecord 的地址** >>> 轻量级锁**
  • Monitor Address 存储 monitor obj 的地址 >>> 重量级锁
    • 由于涉及到了 monitor obj,因此能够认为是重量级的
  • TheadId 线程在 JVM 中的Id >>> 偏向锁

HotspotOverview.pdfide

锁状态的改变

无锁 -> 轻量级锁

CAS!CAS!CAS!CAS!CAS!CAS!CAS!工具

加锁

解锁

偏向锁 -> 轻量级锁

  • 问题:线程重入是如何断定的
    • 由于在 Lock Record Address 中记录了当前持有锁的线程的栈地址,因此能够比对出,是否能够重入

轻量级锁 -> 重量级锁

对象头中锁的状态(我的理解)

重量级锁/轻量级锁/未锁定

相关代码:src.share.vm.runtime.ObjectMonitor

  1. 当 线程-1 请求持有锁时,若是对象时 01 (unlock),则会得到锁,并将对象头中的标志位置为 轻量级锁 (00)
  2. 线程是经过 CAS 自旋的方式去请求锁的
  3. 当对象状态位 00 时,有其余线程请求锁,线程首先经过 CAS 自旋的方式去尝试得到锁,当尝试达到次数没有得到时,对象的状态会变为 10.
  4. 这里再也不使用自旋等待的缘由是自旋会消耗大量资源
  5. 之因此是重量级,是由于要操做两个对象 > 原对象、monitor 对象
  6. 此时对象的 monitor 中,_owner 会变为 线程-1,新的请求线程会放到 _EntryList 中等待
  7. _EntryList 是争抢队列!!!
  8. 线程释放 _owner 会有两种方式
  9. 当持有锁的线程执行完后,会 monitorExit.
  10. 调用对象的 wait 方法后,会进入等待集合 _waiters
  11. 线程要进入 _waiters 队列,须要 _owner 线程调用 wait 方法,因此须要在同步代码块中执行 wait()/notify() 操做
  12. 由于等待集合是 Set,因此 notify 唤醒的时候不肯定唤醒哪一个

偏向锁

优先级: 未锁定 >>> 偏向锁 >>> 轻量级 >>> 重量级

  • 轻量级锁的标志位存在 对象头 的 MarkWord 中,默认是开启的
  • 具体过程
    1. 线程1 请求持有锁,会先检查偏向锁锁的标志位,若是为 1 打开,则经过 CAS 修改线程信息。
    2. 线程信息中记录了当前得到偏向锁的线程的 ThreadId
    3. 若是修改为功,就拿到了锁
    4. 若是经过 CAS 修改失败,且线程状态是 01 未锁定,则升级到轻量级锁被清空标志位
    5. 偏向锁释放,会清空线程信息
  • 为何要用偏向锁
    • 由于 JVM 的设计理念认为,大多数状况下,并不存在锁竞争,不须要频繁修改标志位,减小了变为 重量级锁 的可能性。带来了性能提高。

参考

open jdk


版权声明

image.png

  • 本文做者: 留夕
  • 本文连接: www.yuque.com/diamond/ndv…
  • 版权声明: 本博客全部文章除特别声明外,均采用 CC BY-SA 4.0 许可协议。转载请注明出处!
  • 首发日期: 2019-6-27 21:10:45
相关文章
相关标签/搜索