美文网首页
java 锁相关

java 锁相关

作者: 瓢鳍小虾虎 | 来源:发表于2021-01-11 22:00 被阅读0次

概念:

自旋锁:是指线程在获取锁的时候,如果其他线程已经占用,则等待,循环判断是否可以获得锁,直到获得了锁才会退出循环。

悲观锁:悲观锁假设当前一定会出现并发问题,因此先锁定资源,其他线程无法占用,知道当前线程解锁。
乐观锁:乐观锁假设当前不会出现并发问题,先使用资源,更新资源的时候如果发现被其他线程改过则重新获取。乐观锁其实就是CAS。

共享锁:又叫读锁,所有线程都可以加读锁,就是不能加写锁。
独占锁:又叫写锁,只有当前线程可以读写,其他线程不能读写。

可重入锁:当前线程可以重复进入同一把锁同步的其他代码,与其他线程互斥。本质就是可以多次上锁。可重入锁使用的场景例如递归调用、一个事务中需要调用多个方法,多次方法等。synchronized锁属于可重入锁。
不可重入锁:当前线程不能重复进入同一把锁同步的其他代码。

公平锁、非公平锁:是根据锁争夺规则,遵守先来后到就是公平锁,否则就是非公平锁。

对象锁、类锁、分布式锁:这3种锁是根据作用范围来区分的,分布式是锁针对多个jvm进程的锁。

java中加锁的方式

主要有2种,一个是使用synchronized关键字,另一种是使用J.U.C包中的lock实现类。前者是jvm底层支持,后者是jdk实现的。

synchronized关键字

synchronized是可重入锁、独享锁、悲观锁。

修饰实例方法、静态方法时,隐式指定锁对象。

修饰代码块时,显式指定锁对象。

synchronized关键字的原理

  1. jvm内存结构:

主要有方法区,首先类会被加载到方法区,然后根据类在中创建对象实例。

栈内存着对象的引用,对象头信息存放了类的引用,类中有关于父类子类的引用。

对象的结构具体分为对象头对象体padding,对象头记录对象的运行时相关的信息,比如对象类型(class的引用),是否要被GC,锁状态等,所谓加锁就是修改对象头中的锁相关的属性。

  1. 对象头的结构:

对象头主要分Mark WordKlass PointerArray length,根据32位系统和64位系统,这几部分站位都为32位和64位。

Array length:这部分不是必须的,只有数组才有这部分,用于记录数组长度。通常jvm是可以直接通过对象的元数据获取对象的大小(也就是说类在定义的时候占用空间大小就是可以算出来的),但是数组这种结构不行,所以需要Array length来记录。

Klass Pointer:这部分主要存放类元数据的引用,可以知道当前对象是那种类型。

Mark Word:这里主要存放对象运行时相关的信息,如hashCode, GC标识,分代年龄、是否是偏向锁、锁状态等。

根据当前锁状态,Mark Word的内存结构是可以被改变的。

image.png
  1. 锁升级的过程

由于线程的竞争,对象的锁状态会经历“无锁”->“偏向锁”->“轻量级锁”->“重量级锁”的过程。

锁的升级过程是单向的,不会降级。

1)一个普通的对象如果没被设置为锁的时候,锁标志位=01,是否偏向锁=0

2)当一个对象被设置为同步锁(synchronized),并且有一个线程A抢到的时候,锁标志位=01,是否偏向锁=1,同时Mark Word会记录当前线程A
的id,这就是偏向锁

3)如果这个线程A再次尝试获取此对象的锁,因为当前对象已经是偏向锁并且记录的线程id没变,所以线程A可以再次执行锁内的代码。

4)如果此时线程B尝试获取此对象的锁,就会出现锁竞争,就是线程B会通过CAS的方式不停的尝试获取锁一定次数(自旋),这个次数是jvm自己控制的,如果成功,则Mark Word记录的线程id变为B的线程id,由B执行锁内的代码。

5)如果B争抢不到,则对象的锁会升级为轻量级锁。此时jvm会在当前线程A的栈内开辟一个单独的空间,用来保存当前对象的Mark Word地址,同时Mark Word 中也会保存A栈中这块空间的地址,当前对象的锁标志位变成00,存储结构也随之变化。轻量级锁互相引用的过程同样也遵循着CAS的方式,也就是说只要A给B机会,对象随时都可以易主。

6)如果线程B还是争抢不到锁,则对象会继续升级为重量级锁重量级锁的处理方式不再是“CAS+自旋”,而是直接阻塞未抢到锁的线程。

7)重量级锁执行流程:

首先重量级锁中线程的管理交给了一个特有的对象,叫Monitor(对象监视器)。

Monitor有几个主要结构:

  • Owner 用来记录当前占有锁的线程。Owner的占有和释放的过程中Monitor还会有一个+1计数的行为,用来处理当前线程重复锁的情况,即重复锁了几次就要释放几次才算释放。
  • EntryList 等待线程队列,这个队列遵循先来后到,如果Owner指向为null,Monitor会取这个队列尾部的那个线程让他参与Owner竞争(还是CAS方式),如果竞争失败则还得放回队列头部。
  • WaitSet 当线程被程序挂起的时候,会进入这个队列,直到被唤醒才会参与Owner的竞争(CAS方式),如果竞争失败,会被放到EntryList排队。

首先线程会通过CAS争抢Owner,争到的线程Owner会记录这个线程的地址,计数+1,其他没有争到的线程会被安排到EntryList排队等待,执行过程中如果程序主动挂起了线程,则当前线程退出Owner占有权,计数 -1,放到WaitSet中等待唤醒,被唤醒后重新参与竞争。

虽然EntryList是有序的,但是没进入EntryList的线程依然都是通过CAS方式参与Owner竞争的,所以重量级锁并不是公平锁。

关于偏向锁的竞争,需要在合适的时间点,我们称之为“安全点”,在这个安全点发生的事情:
1)当前时间片没有正在执行字节码
2)检查当前线程是否存活,存活则会把自己设置为无锁状态,让所有线程竞争;不存活,则直接偏向最新的线程

关于读写锁 ReadWriteLock

  • 最重要的一点是读锁和写锁是互斥的,有线程上了读锁,就不能再备上写锁,反之亦然。

  • 读锁可以多个线程同时上锁,互不影响。

可重入锁参考内容:https://www.zhihu.com/question/23284564
对象内存结构参考内容:
https://www.dazhuanlan.com/2020/02/02/5e364cd8d6996/
https://www.jianshu.com/p/3d38cba67f8b
https://blog.csdn.net/lengxiao1993/article/details/81568130
关于锁的升级过程细节:
https://blog.csdn.net/lengxiao1993/article/details/81568130
https://blog.51cto.com/14440216/2427707

相关文章

  • 2019大厂面试存疑

    Java相关 Java实现的锁有哪些synchornized实现的监视器锁、ReentrantLock、ReadW...

  • 偏向锁原理

    1 概述 本文介绍偏向锁相关原理,并不限定于Java中的偏向锁,但是Java中偏向锁的实现也是相同的原理,本文主要...

  • Java锁相关知识

    从ReentrantLock入手,学习Java锁相关知识 首先来看一下Java锁的使用 ReentrantLock...

  • Redis 分布式锁

    为什么需要分布式锁? 在传统单体应用单机部署的情况下,可以使用Java并发相关的锁,如ReentrantLcok或...

  • 利用Redis实现分布式锁

    为什么需要分布式锁? 在传统单体应用单机部署的情况下,可以使用Java并发相关的锁,如ReentrantLcok或...

  • java对象头及相关锁

  • java并发-独占锁与共享锁,公平锁与非公平锁,重入锁与非重入锁

    java并发-乐观锁与悲观锁,独占锁与共享锁,公平锁与非公平锁,重入锁与非重入锁 java 中的锁 -- 偏向锁、...

  • Java中的锁

    参考: java 中的锁 -- 偏向锁、轻量级锁、自旋锁、重量级锁 Java中的锁--朱小厮 乐观锁和悲观锁 宏观...

  • Java中的锁

    Java中的锁 参考了这篇:Java中的锁分类 公平锁/非公平锁(ReentrantLock/Synchroniz...

  • 看完你就知道的乐观锁和悲观锁

    看完你就知道的乐观锁和悲观锁 Java 锁之乐观锁和悲观锁 [TOC] Java 按照锁的实现分为乐观锁和悲观锁,...

网友评论

      本文标题:java 锁相关

      本文链接:https://www.haomeiwen.com/subject/vpndaktx.html