锁之一二事

8 minute read

背景

相关技术栈: Java JVM

线程安全性

线程安全的代码,核心在于对状态访问操作进行管理,特别是共享和可变状态的访问。对象的状态是指存储在实例域或静态域中的数据。一个类如果没有任何域,也没有任何对其它类中域的引用,计算过程中的临时状态存储在线程栈上的局部变量中,并且只能由正在执行的线程访问,则这个对象是线程安全的。

在实际情况中,应尽可能使用线程安全对象管理类的状态。

以关键字synchronized修饰的方法是一种横跨整个方法体的同步代码块,其中该同步代码块的锁是方法调用所在的对象,静态的synchronized方法以Class对象作为锁。每个Java对象都可以用作一个实现同步的锁,也就是所说的内置锁。 Java内置锁是互斥的,也是可重入的。可重入是指如果某个线程视图获得一个由它自己持有的锁,不会被阻塞,也就说获取锁的粒度是线程。之所以每个对象都有一个内置锁,是为了免去显式创建锁的过程。

if(!vector.contains(element) vector.add(element)

虽然synchronized方法可以确保单个操作的原子性,但假如像上述代码把多个操作合并为一个复合操作,仍需要额外的加锁机制。

对象的共享

同步不仅意味着原子性,同时还有一个很重要的方面,内存可见性。线程安全不仅希望防止某个线程在使用对象状态而另一个线程同时在修改状态,而且希望确保当一个线程修改了对象状态后,其它线程能看到发生的状态变化。 为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制。 PS:Java内存模型要求,变量的读取操作和写入操作都必须是原子操作,对于volatile类型的long和double变量,JVM允许将64位的读操作或写操作分解为两个32位的操作。当读取到一个非volatile类型的long变量时,如果对该变量的读操作和写操作在不同的线程中执行,可能会读取到某个值的高32位和另一个值的低32位。 为什么访问某个共享且可变的变量要求在同一个锁上同步?为了确保某个线程写入该变量的值对其它线程来说都是可见的,否则一个线程在未持有正确锁的情况下读取某个变量,读到的可能是一个失效值。 加锁的含义不仅仅局限于互斥行为,还包括内存可见性。为了确保线程都能看到共享变量的最新值,所有执行读操作或者写操作的线程都必须在同一个锁上同步。

volatile变量 当把变量声明为Volatile类型后,编译和运行时会禁止指令重排序。volatile变量不会被缓存在寄存器或者其它处理器不可见的地方,因此读取volatile变量总会返回最新写入的值。 volatile变量不能保证原子性,只能保证可见性,典型的应用场景用作状态标记判断是否退出循环。 volatile boolean asleep while(!asleep) exec(); 只有当满足以下所有条件,才能使用volatile变量: 1、对变量的写入操作不依赖变量当前值 2、该变量不与其它变量一起纳入不变性条件 3、访问变量时不需要加锁

ThreadLocal类 ThreadLocal对象通常用于防止对可变的单实例对象或全局变量进行共享,这个类能使线程中的某个值与保存值的对象关联起来,ThreadLocal提供get或set等访问接口或方法,这些方法为每个使用该变量的线程存有一份独立的副本,因此get总能返回由当前执行线程在调用set时设置的最新值。所以,可以理解ThreadLocal视为Map<Thread, t>对象, 不过具体实现并不是这样,这些特定于线程的值保存在Thread对象中,当线程终止后,对象会作为垃圾回收。 个人理解,TreadLocal对象适合存储那种稀缺全局资源,例如数据库连接等。

显式锁

Java5.0增加ReentrantLock,不是为了替代内置锁,而是当内置锁不适用时,作为一种可选择的高级功能。 与内置锁机制不同,Lock提供一种无条件的、可轮询的、定时的及可中断的锁获取操作,所有加锁和解锁都是显式的。ReentrantLock实现了Lock接口,提供与synchronized相同的互斥性和内存可见性。 内置锁的局限性,无法中断一个正在等待获取锁的线程。Lock接口的标准使用形式,在finally块中释放锁。 可定时的与可轮询的锁获取模式是由tryLock方法实现,可定时的与可轮询的锁提供另一种选择,避免死锁的发生。

如果线程不能获取到所有需要的锁,那么可使用可定时的活可轮询的锁获取方式,重新获得控制权,它会释放已经获得的锁,然后重新尝试获取所有锁。 在实现具有时间限制的操作中,定时锁同样非常有用。当在带有时间限制的操作中调用了一个阻塞方法时,它会根据剩余时间提供一个期限,如果操作不能在给定的时间内给出结果,会使程序提前结束。 正如定时的锁获取操作能在带有时间限制的操作中使用独占锁,可中断的锁获取操作同样能在可取消的操作中使用加锁。lockInterruptibly方法能在获得锁的同时保持对中断的响应。

ReentrantLock默认是非公平锁。公平锁是指按照线程发出请求的顺序来获取锁,而非公平锁是指当线程发出请求锁的时候,如果在发出请求的同时该锁的状态变为可用,那么这个线程将跳过队列中所有的等待线程并获得这个锁。 在大多数情况下,非公平锁的性能要高于公平锁的性能,在激烈竞争的情况下,吞吐量会提高。在恢复一个被挂起的线程与该线程真正开始运行之间存在着严重的延迟,有可能存在当唤醒一个等待的线程的时候,可以请求、使用、执行和释放线程。 当持有锁的时间相对较长,或者请求锁的平均时间间隔较长,应该使用公平锁。

仅当内置锁不能满足需求时,才考虑使用ReentrantLock。在一些内置锁无法满足需求的情况下,当需要一些高级功能时,应该使用ReentrantLock,这些功能包括:可定时的、可轮询的与可中断的锁获取操作,公平队列,以及非块结构的锁。

ReentrantLock实现了标准的互斥锁,每次最多只有一个线程持有ReentrantLock。

读-写锁 ReadWriteLock public interface ReadWriteLock { Lock readLock(); Lock writeLock();}

读写锁是一种性能优化措施,实际情况下,对于在多处理器系统上被频繁读取的数据结构,读-写锁能提高性能。其它情况下,读-写锁的性能比独占锁的性能略差一些,因为复杂性更高。

ReentrantReadWriteLock为这两种锁提供了可重入的加锁语义,构造时也可以选择是否公平, 如果这个锁由读线程持有,而另一个线程请求写入锁,那其它读线程都不能获得读取锁,直到写线程使用完并且释放了写入锁。写线程降级为读线程可以,读线程升级为写线程不行,这样做会导致死锁。

当锁的持有时间较长且大部分操作都不会修改被守护的资源时,读-写锁能提高并发性。

与内置锁相比,显式的Lock提供了一些扩展功能,在处理锁的不可用性方面有着更高的灵活性。ReentrantLock不能完全替代synchronized,只有在synchronized无法满足需求时,才应该使用它。

博客总结

如果一个线程调用了一个对象的同步方法,例如synchronized xxx()这种形式,那么这个线程不仅持有该类对象的锁,同时持有该父类对象的锁,因此其它线程不能访问父类中的其它同步方法,调用线程会进入阻塞状态。 https://blog.csdn.net/u014745069/article/details/80753363

线程死锁

public class DeadlockSample {

//必须有两个可以被加锁的对象才能产生死锁,只有一个不会产生死锁问题
private final Object obj1 = new Object();
private final Object obj2 = new Object();


//先synchronized  obj1,再synchronized  obj2
private void calLock_Obj1_First() {
    synchronized (obj1) {
        Thread.sleep(5);
        synchronized (obj2) {
            sleep();
        }
    }
}

//先synchronized  obj2,再synchronized  obj1
private void calLock_Obj2_First() {
    synchronized (obj2) {
        Thread.sleep(5);
        synchronized (obj1) {
            sleep();
        }
    }
} }

可重入锁 可重入锁的使用场景? 避免死锁 https://blog.csdn.net/startyangu/article/details/83933416 当一个线程执行一个带锁的代码块或方法,同时代码块或方法里也获取同一个锁。为了避免死锁,此时就可以用可重入锁

可重入锁的实现原理? 可重入锁分为公平和非公平两种实现方式 https://blog.csdn.net/yanyan19880509/article/details/52345422 有一个volatile类型的变量state记录锁的状态,当一个线程获取到锁的时候,state+1,当这个线程再次请求该锁的时候,例如线程再次调用了该类的 synchronized方法,state再次加1,表示拥有该锁的线程数量,只有当state=0的时候,才表示该锁已经被释放,允许其它线程竞争该锁。 非公平锁和公平锁的区别在于每个锁都有一个队列,公平锁是线程先入队列去排队,非公平锁是线程先去检测下锁释放已经释放,如果已经释放,就参与竞争锁。 由于当一个锁释放的时候,唤醒队列里的第一个线程和线程被唤醒后需要参与竞争锁都需要时间,非公平锁就是利用的这段时间提高线程执行效率。

读写锁 读写锁的使用场景? 高频读/低频写的应用场景 两个或多个线程可以同时进行读操作 线程正在进行读操作,另一个线程想要进行写操作,另一个线程将会被阻塞直到所有读操作结束 线程正在进行写操作,另一个线程想要进行操作(读或写),另一个线程将会阻塞直到写入方完成操作

1.8添加了一种新的读写锁实现方式 StampedLock https://blog.csdn.net/u011943534/article/details/83819410

读写锁和互斥锁的区别? 读写锁允许多个线程共享临界区,互斥锁不允许,这是读多写少场景下性能优于互斥锁的原因

ReadWriteLock 是一个接口,它的实现类是 ReentrantReadWriteLock,通过名字你应该就能判断出来,它是支持可重入的。 有一点需要注意,那就是只有写锁支持条件变量,读锁是不支持条件变量的,读锁调用 newCondition() 会抛出 UnsupportedOperationException 异常。

分段锁 分段锁的使用场景? https://www.cnblogs.com/hi3254014978/p/12335100.html 提高并发性能,具体实现为ConcurrentHashMap ConcurrentHashMap中存放的数据是一段段的,由多个segment组成,类似于数组加链表 ConcurrentHashMap中有三个参数 initialCapacity:初始总容量,默认16 loadFactor:加载因子,默认0.75 concurrencyLevel:并发级别,默认16 并发级别控制了Segment的个数,在一个ConcurrentHashMap创建后Segment的个数是不能变的,扩容过程过改变的是每个Segment的大小。

在jdk1.7中,段Segment继承了重入锁ReentrantLock,有了锁的功能,每个锁控制的是一段。 在jdk1.8中,ConcurrentHashmap采用的底层数据结构为数组+链表+红黑树的形式。数组可以扩容,链表可以转化为红黑树。 其中,对segment的加锁用cas+synchronized替换ReentrantLock? 为什么? 减少内存开销:如果使用ReentrantLock则需要节点继承AQS来获得同步支持,增加内存开销,而1.8中只有头节点需要进行同步。 内部优化:synchronized则是JVM直接支持的,JVM能够在运行时作出相应的优化措施:锁粗化、锁消除、锁自旋等等。

分段锁的缺点和优点? 分段锁的优势在于保证在操作不同段 map 的时候可以并发执行,操作同段 map 的时候,进行锁的竞争和等待。 缺点在于分成很多段时会比较浪费内存空间(不连续,碎片化); 操作map时竞争同一个分段锁的概率非常小时,分段锁反而会造成更新等操作的长时间等待; 当某个段很大时,分段锁的性能会下降。

悲观锁和乐观锁 https://juejin.cn/post/6844903639207641096 什么是悲观锁和乐观锁? 悲观锁:每次在获取数据时都要加锁,期间对该数据进行读写的其它线程需要等待 乐观锁:获取数据的时候不会进行加锁,更新数据的时候需要判断该数据是否被修改过,如果数据被其他线程修改,则不进行数据更新,如果数据没有被其他线程修改,则进行数据更新。由于数据没有进行加锁,期间该数据可以被其他线程进行读写操作。 使用场景是什么? 悲观锁适合写入操作频繁的场景,如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁的开销,降低了系统的吞吐量。 乐观锁适合读取操作频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。 具体实现是什么? Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。 Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

乐观锁的实际场景:在线多人编辑

乐观锁常见的两种实现方式?

  1. 版本号机制
  2. CAS算法 CAS算法涉及到三个操作数 需要读写的内存值 V 进行比较的值 A 拟写入的新值 B 当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。

乐观锁的缺点? 1、ABA问题(仅CAS会有) 解决方法:JDK 1.5 以后的 AtomicStampedReference 类就提供了此种能力,其中的 compareAndSet 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。 2、循环时间长开销大 如果JVM能支持处理器提供的pause指令那么效率会有一定的提升 3 只能保证一个共享变量的原子操作 从 JDK 1.5开始,提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用AtomicReference类把多个共享变量合并成一个共享变量来操作。

CAS和synchronized的比较? 其实是乐观锁思想和悲观锁思想的比较 对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源; 而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。 对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。

synchronized锁的升级和优化 jdk1.6之后,对synchronized锁进行了优化,主要包括为了减少获得锁和释放锁带来的性能消耗而引入的 偏向锁 和 轻量级锁 以及其它各种优化之后变得在某些情况下并不是那么重了。 synchronized的底层实现主要依靠 Lock-Free 的队列,基本思路是 自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。 在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。

面试题:ReentrantLock内部实现也是使用CAS加自旋,为什么会成为悲观锁? 因为ReentrantLock只是内部实现用到了CAS和自旋来提高性能,但是从其使用角度来看,是用到了悲观锁的思想。

锁的综述 https://www.cnblogs.com/xiaolincoding/p/13675202.html 对于互斥锁加锁失败而阻塞的现象,是由操作系统内核实现的。当加锁失败时,内核会将线程置为「睡眠」状态,等到锁被释放后,内核会在合适的时机唤醒线程,当这个线程成功获取到锁后,于是就可以继续执行。

互斥锁为什么会有两次线程上下文切换的成本? 当线程加锁失败时,内核会把线程的状态从「运行」状态设置为「睡眠」状态,然后把 CPU 切换给其他线程运行; 接着,当锁被释放时,之前「睡眠」状态的线程会变为「就绪」状态,然后内核会在合适的时间,把 CPU 切换给该线程运行。

线程上下文切换的成本是什么? 当两个线程是属于同一个进程,因为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不动,只需要切换线程的私有数据、寄存器等不共享的数据。 重要的wiki写三遍 https://www.cnblogs.com/xiaolincoding/p/13675202.html https://www.cnblogs.com/xiaolincoding/p/13675202.html https://www.cnblogs.com/xiaolincoding/p/13675202.html 使用自旋锁的时候,当发生多线程竞争锁的情况,加锁失败的线程会「忙等待」,直到它拿到锁。这里的「忙等待」可以用 while 循环等待实现,不过最好是使用 CPU 提供的 PAUSE 指令来实现「忙等待」,因为可以减少循环等待时的耗电量。

自旋锁需要抢占式的调度器

synchronized关键字 synchronized的使用场景? Synchronized修饰普通同步方法:锁对象当前实例对象; Synchronized修饰静态同步方法:锁对象是当前的类Class对象; Synchronized修饰同步代码块:锁对象是Synchronized后面括号里配置的对象,这个对象可以是某个对象(xlock),也可以是某个类(Xlock.class); 注意点: 1、使用synchronized修饰非静态方法或者使用synchronized修饰代码块时制定的为实例对象时,同一个类的不同对象拥有自己的锁 2、使用使用synchronized修饰实例对象时,如果一个线程正在访问实例对象的一个synchronized方法时,其它线程不仅不能访问该synchronized方法,该对象的其它synchronized方法也不能访问,因为一个对象只有一个监视器锁对象,但是其它线程可以访问该对象的非synchronized方法。 3、线程A访问实例对象的非static synchronized方法时,线程B也可以同时访问实例对象的static synchronized方法,因为前者获取的是实例对象的监视器锁,而后者获取的是类对象的监视器锁,两者不存在互斥关系。

synchronized的锁状态有四种:无锁、偏向锁、轻量级锁、重量级锁 为什么对synchronized进行了锁状态升级的优化? synchronized的最初实现方式是重量级锁的实现方式,但阻塞和唤醒一个线程需要操作系统和CPU,如果同步代码块中的时间很短,甚至比状态切换的时间短,这就严重影响了效率。

synchronized的实现原理? https://segmentfault.com/a/1190000022904663 synchronized的锁与Java的对象头有关。对象头包括两部分数据,mark word标记字段和klass pointer类型指针 mark word:存储的内容与锁的状态有关,默认存储hashcode、分代年龄、锁标志位信息 klass pointer:指向类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。 mark word存储内容 无锁:hashcode、分代年龄、锁标志位 偏向锁:存储持有偏向锁的线程ID 轻量级锁:指向栈中锁记录的指针 重量级锁:指向重量级锁的指针

synchronized的实现是通过对象内部的一个监视器锁monitor实现的,监视器锁本质依赖操作系统的mutex lock互斥锁实现的,操作系统比较对象头中的mark word中的锁标志位来判断并实现锁升级的过程。

synchronized锁升级的过程?https://juejin.cn/post/6844903813665538062#comment 持有synchronized同步代码的线程在第一次执行的时候,锁状态变为偏向锁,修改对象头中的mark word为持有该偏向锁的线程ID,但偏向锁不会主动释放,如果这个线程再次执行同步代码块,只需比较对象头中存储的线程ID 是否是自己,如果是,直接执行,如果不是,需要判断对象头中记录的线程是否存活,如果没有存活,锁对象重置为无锁状态,如果存活,进行锁升级为轻量级锁(所以偏向锁对于那些只有一个线程在执行同步代码块的线程有很高效率); 轻量级锁其实是一个自旋锁,在轻量级锁的状态下,另一个线程其实就是通过不断的执行cas操作去修改对象头的标志位,当然不停自旋肯定占用CPU时间,当自旋超过一定次数或者有第三个线程来请求的时候,偏向锁升级为重量级锁 重量级锁通过对象内部的监视器(monitor)实现,而其中 monitor 的本质是依赖于底层操作系统的 Mutex Lock 实现,操作系统实现线程之间的切换需要从用户态切换到内核态,切换成本非常高。 在自旋的时候,jdk又进行了优化,引入了自适应自旋锁,意味着自旋次数不再固定。自适应自旋锁意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。 如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。

偏向锁通过对比 Mark Word 解决加锁问题,避免执行CAS操作。 轻量级锁是通过用 CAS 操作和自旋来解决加锁问题,避免线程阻塞和唤醒而影响性能。 重量级锁是将除了拥有锁的线程以外的线程都阻塞。 https://blog.csdn.net/tongdanping/article/details/79647337 PS:为了避免无用的自旋,轻量级锁一旦膨胀为重量级锁就不会再降级为轻量级锁了;偏向锁升级为轻量级锁也不能再降级为偏向锁。一句话就是锁可以升级不可以降级,但是偏向锁状态可以被重置为无锁状态。

参考

《深入理解Java虚拟机:JVM高级特性与最佳实践(第二版)》

volatile https://juejin.cn/post/6844903989998288910

reentrantlock 和 condition 的使用方法? https://developer.aliyun.com/article/776793 Condition 中的 await() 方法相当于 Object 的 wait() 方法,Condition 中的 signal() 方法相当于Object 的 notify() 方法,Condition 中的 signalAll() 相当于 Object 的 notifyAll() 方法。 不同的是,Object 中的 wait(),notify(),notifyAll()方法是和”同步锁”(synchronized关键字)捆绑使用的;而 Condition 是需要与”互斥锁”/”共享锁”捆绑使用的。

reentrantLock是如何实现的?
https://juejin.cn/post/6844903853494632462 AQS AbstractQueuedSynchronizer ReentrantLock的可重入功能基于AQS的同步状态:state。 其原理大致为:当某一线程获取锁后,将state值+1,并记录下当前持有锁的线程,再有线程来获取锁时,判断这个线程与持有锁的线程是否是同一个线程,如果是,将state值再+1,如果不是,阻塞线程。 当线程释放锁时,将state值-1,当state值减为0时,表示当前线程彻底释放了锁,然后将记录当前持有锁的线程的那个字段设置为null,并唤醒其他线程,使其重新竞争锁。

aqs的工作原理是什么? https://blog.csdn.net/javazejian/article/details/75043422 AbstractQueuedSynchronizer又称为队列同步器(后面简称AQS),它是用来构建锁或其他同步组件的基础框架,内部通过一个int类型的成员变量state来控制同步状态, 当state=0时,则说明没有任何线程占有共享资源的锁,当state=1时,则说明有线程目前正在使用共享变量,其他线程必须加入同步队列进行等待, AQS内部通过内部类Node构成FIFO的同步队列来完成线程获取锁的排队工作,同时利用内部类ConditionObject构建等待队列, 当Condition调用wait()方法后,线程将会加入等待队列中,而当Condition调用signal()方法后,线程将从等待队列转移动同步队列中进行锁竞争。 注意这里涉及到两种队列,一种的同步队列,当线程请求锁而等待的后将加入同步队列等待,而另一种则是等待队列(可有多个),通过Condition调用await()方法释放锁后,将加入等待队列。

jdk1.5之前synchronized是重量级锁,jdk1.6对synchronized进行了各种优化,并不会那么笨重。 https://juejin.cn/post/6844903640197513230 synchronized能保证原子性、可见性、有序性

synchronized可以把任何一个非null对象作为锁,在hotspot jvm实现中,锁有个专门的名字:对象监视器object monitor synchronized有三种用法 作用在实例方法,monitor是实例对象 作用在静态方法,monitor是class对象 作用在某一对象实例,monitor是该对象实例

synchronized是一种对象锁,作用粒度是对象,所以如果两个线程如果调用的是同一个对象的不同synchronized方法,也是会被阻塞的

synchronized的底层实现原理? synchronized依赖jvm,juc lock依赖硬件指令 每个对象都是一个monitor(监视器锁),当monitor被占用时就会处于锁定状态,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象 对于同步代码块 线程执行monitorenter指令会尝试获得monitor的所有权,线程执行monitorexit后如果为0,线程退出monitor,其它被这个monitor阻塞的线程尝试获取monitor控制权 对于同步方法 相比于同步方法,其常量池中多了ACC_SYNCHRONIZED标识符,jvm根据该标识符实现方法同步

monitorenter/monitorexit指令是jvm通过调用操作系统的命令mutex来实现,被阻塞的线程会被挂起、等待重新调度,会导致“用户态和内核态”两个态之间来回切换,对性能有较大影响。

monitor和Java对象头的关系是什么? 任何一个对象都有一个monitor与之关联,当且仅当一个monitor被持有后,对象将处于锁定状态,synchronized在jvm里的实现是基于进入和退出monitor对象来实现同步 monitor可以被认为是一个对象,所有java对象天生都是的monitor,每一个Java对象都有成为monitor的潜质,每一个java对象都带有一把看不见的锁,成为monitor锁 对象头包括mark word和class pointor, class pointor存储指向类元数据的指针,mark word是实现偏向锁和自旋锁的关键 mark word存储对象自身的运行时数据,比如hashcode、分代年龄、锁状态信息等,markword会根据对象的状态复用空间 mark word最后两位是 是否是偏向锁和锁标志位 锁的4中状态:无锁状态、偏向锁状态、自旋锁状态、重量级锁状态(级别从低到高) 偏向锁会存储当前占用此对象的线程id 自旋锁会存储执行线程栈中锁记录的指针

管程模型? https://zhuanlan.zhihu.com/p/87724639

Updated:

Leave a comment