【Java】synchronized详解

synchronized详解

*-*Qw6IX发布于 今天 09:53

synchronized介绍

synchronized相信大家都比较熟悉,面试的时候也是必备问题。有很多书籍介绍它,网上也有很多很多相关的文字,我自己也看过很多。但是看了就忘,印象不深,于是我决定阅读源码来加深记忆。代码出真知,这一看不要紧,不仅让我对synchronized有了更深入的理解,我还发现,网上许多文章,甚至很多面试官的理解都是错误的。

比如,说说你对自旋锁的理解。你可以先把答案自己在心里想一下,看到下面,你可能会发现,原来你原来的理解是错误的。

synchronized使用

使用上我就不多说了,相信大家都知道,这里概括一下

synchronized(obj) {

....

some code

...

}

这种使用时对obj对象上锁。也是比较常见的使用。

synchronized void test() {

....

some code

...

}

这种事对this进行上锁

synchronized static void test() {

....

some code

...

}

这种事对Class对象上锁。

synchronized字节码表示

加入我们有一个简单的方法如下

public void test() {

Object obj = new Object();

synchronized (obj) {

System.out.println("in locking");

}

}

会编译成以下字节码

 public test()V

...

MONITORENTER

L0

LINENUMBER 15 L0

GETSTATIC java/lang/System.out : Ljava/io/PrintStream;

LDC "in locking"

INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V

L6

LINENUMBER 16 L6

ALOAD 2

MONITOREXIT

L1

GOTO L7

L2

FRAME FULL [com/pinduoduo/service/jmm/JmmTest java/lang/Object java/lang/Object] [java/lang/Throwable]

ASTORE 3

ALOAD 2

MONITOREXIT

...

}

其他我们不要管,可以看到字节码中有一个MONITOREXIT指令和两个MONITOREXIT指令。两个MONITOREXIT是因为要异常的情况也也要进行MONITOREXIT.

synchronized的jvm表示

我们在上小节知道了synchronized块是有MONITORENTERMONITOREXIT指令包裹着的,那这两个指令怎么执行呢?这就涉及的jvm怎么实现的了,这里我们就以hotspot为例,看看他是怎么实现的。

hotspot的这两个指令执行入口在bytecodeInterpreter.cpp中的CASE(_monitorenter)CASE(_monitorexit)中,接着就是一堆c++代码了,这里我们不深入,不然大家肯定睡着了,有兴趣的小伙伴可以自己去看看,我其他文章也会具体分析。

synchronized原理

这一小节会简单的说一说synchronized的原理,都是根据我阅读源码的总结。

偏向锁

偏向锁适用于没有多个线程会进入的资源。这个点在说轻量级锁的时候会再次提到。

偏向锁又称无锁,因为它只改变锁对象头的markWord。

结构图如下:
【Java】synchronized详解

偏向锁状态下,markword的锁标志位会变成01,是否偏向标志为1,此外epoch只一个标志,用于是否需要批量重定向的判断,我们先不管它。那如何证明偏向锁状态下,锁的markword会变成这个样子呢?我们的jol就登场了,跟我一起看接下来的小demo,耐心。

首先我们引入jol的依赖

 <dependency>

<groupId>org.openjdk.jol</groupId>

<artifactId>jol-core</artifactId>

<version>0.9</version>

</dependency>

代码如下

import org.openjdk.jol.info.ClassLayout;

import org.openjdk.jol.vm.VM;

public class MarkWordTest {

public static void main(String[] args) throws InterruptedException {

try {

Thread.sleep(5000);

} catch (InterruptedException e) {

e.printStackTrace();

}

MarkWordTest object = new MarkWordTest();

String classLayout1 = ClassLayout.parseInstance(object).toPrintable();

System.out.println("---------------------------加锁前---------------------------");

System.out.println(classLayout1);

System.out.println("------------------------------------------------------------");

synchronized (object) {

//打印当前jvm信息

System.out.println("---------------------------加锁中---------------------------");

String classLayout2 = ClassLayout.parseInstance(object).toPrintable();

System.out.println(classLayout2);

System.out.println("------------------------------------------------------------");

}

System.out.println("---------------------------加锁后---------------------------");

String classLayout3 = ClassLayout.parseInstance(object).toPrintable();

System.out.println(classLayout3);

System.out.println("------------------------------------------------------------");

}

}

首先注意到,代码开始有一个sleep 5秒的操作,是因为偏向锁默认有个延迟启动时间,默认为4s。

然后分别在加锁前、中、后对对象头进行打印,我们看一下打印结果

---------------------------加锁前---------------------------

com.pinduoduo.service.jmm.MarkWordTest object internals:

OFFSET SIZE TYPE DESCRIPTION VALUE

0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)

4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)

8 4 (object header) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)

12 4 (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

------------------------------------------------------------

---------------------------加锁中---------------------------

com.pinduoduo.service.jmm.MarkWordTest object internals:

OFFSET SIZE TYPE DESCRIPTION VALUE

0 4 (object header) 05 38 67 03 (00000101 00111000 01100111 00000011) (57096197)

4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)

8 4 (object header) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)

12 4 (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

------------------------------------------------------------

---------------------------加锁后---------------------------

com.pinduoduo.service.jmm.MarkWordTest object internals:

OFFSET SIZE TYPE DESCRIPTION VALUE

0 4 (object header) 05 38 67 03 (00000101 00111000 01100111 00000011) (57096197)

4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)

8 4 (object header) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)

12 4 (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

------------------------------------------------------------

Process finished with exit code 0

markword是64位,既上面一堆数字的前两行,我们删掉其他东西,只看markword

---------------------------加锁前---------------------------

...

0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)

4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)

...

------------------------------------------------------------

---------------------------加锁中---------------------------

...

0 4 (object header) 05 38 67 03 (00000101 00111000 01100111 00000011) (57096197)

4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)

...

------------------------------------------------------------

---------------------------加锁后---------------------------

...

0 4 (object header) 05 38 67 03 (00000101 00111000 01100111 00000011) (57096197)

4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)

...

------------------------------------------------------------

是不是有点看不懂,这是因为,Hopspot是小端储存,既低位值放在高位地址上。那我们按照我们好读的宗旨把它改为大端存

---------------------------加锁前---------------------------

00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101

------------------------------------------------------------

---------------------------加锁中---------------------------

00000000 00000000 00000000 00000000 00000011 01100111 00111000 00000101

------------------------------------------------------------

---------------------------加锁后---------------------------

00000000 00000000 00000000 00000000 00000011 01100111 00111000 00000101

------------------------------------------------------------

现在就是我们可以读懂的结构了。

等等,加锁前为啥锁标志位为01,偏向锁模式呢?是的,是这样的,对象头初始就是偏向锁模式,并不是很多文章都说的无锁模式,只是现在还没有指向任何线程。

加锁中我们就好理解了,锁标志位是偏向锁,并且线程id也不是0了。

加锁后可以看到,markword并没有任何改变。这样做自然有用,就是,当有其他线程来想进入临界区的时候,看到偏向线程id不是自己,直接进行锁升级(不考虑批量重偏向)。

至此我们可以得出一个结论:偏向锁只在只有一个线程会进入临界区的场景有用。

不知道你有没有注意到一个问题,就是,既然退出临界区后,markword没有任何变化,那锁重入的计数怎么实现的?这就涉及到synchronized的重入了,不管是偏向锁、轻量锁还是重量级锁,都是使用的同一套重入机制。

自旋锁的重入

在Hopspot中,每个线程都有一个锁记录表,锁记录是BasicObjectLock类型,里边有一个obj和displaced_header,obj存储锁对象,set_displaced_header锁对象的markword,每一次进入临界区,都会创建一个BasicObjectLock对象,如果是重复,那displaced_header就置为null.

【Java】synchronized详解

轻量级锁

轻量级锁适用于线程交替进入临界区的场景,只要有锁竞争就会升级成重量级锁。注意,1.8中<font color=red>没有自旋锁</font>,别再说经过自旋后升级成重量级锁!!!

别嫌我啰嗦,再比较一下偏向锁与轻量级锁适用场景的不同。

偏向锁:只有一个线程会进入临界区

轻量级锁:不同线程交替进入临界区

偏向锁和轻量级锁都是在没有锁竞争的条件下适用的。

轻量级锁的加锁和释放都是通过cas操作对锁对象的markword就行修改而实现的,它的重入与偏向锁的机制相同,这里就不赘述了。

重量级锁

重量级锁的实现是用了监视器模式(moniter),我猜字节码指令MONITORENTERMONITOREXIT开始指的就是这个操作,后来经过一系列的优化,使这两个指令的字面意义跟锁的实现不太对应了。

在Hotspot中,每个对象(java对象)都会关联一个ObjectMonitor,这个就是一个监视器。

ObjectMonitor中我们重点关注以下几个字段

 ObjectMonitor() {

_owner = NULL; // 锁持有者

_recursions = 0; // 重入次数

_WaitSet = NULL;

_cxq = NULL ;

_EntryList = NULL ;

}

_owner既为锁持有者。

_recursions表示重入次数。从这个字段就可以看出来,重量级锁的重入跟轻量级锁、偏向锁的重入不同,是用计数法来做的。

_cxq是一个队列,如果线程获取锁失败,一定是进入这个队列的。

_EntryList也是一个人队列,当ObjectMonitor持有者释放锁后,ObjectMonitor一定是从这个队列中唤醒一个线程的。

_WaitSet是调用wait操作的线程队列。

认识这几个字段后,我们会很容易理解ObjectMonitor的运作原来,并没有网上说的那么复杂,至于网上经常晒的那张流程图我就不贴了。下面我概括一下。

  • 线程要进入临界区的时候,如果_owner为空,则直接用cas去修改_owner的值,成功则证明获取锁成功,否则,会准备进去队列,在进入队列前,会经过多次自旋尝试cas修改_owner,这也是为什么synchronized,是非公平锁的原因,就是他会不断的抢占式的获取锁。
  • 多次尝试无果后,线程会进入_cxq队列中。注意,抢占无果的线程一定是进入_cxq队列。
  • 线程在获取锁后,如果调用lock#wait方法,那这个线程会进入_WaitSet队列。
  • 当获取锁的线程调notify或notifyAll时,根据不同的策略,_WaitSet会将节点插入_cxq_EntryList中。

    0: 将头节点插入到EntryList头部

    1: 将头节点插入到EntryList尾部

    2: 将头节点插入到cxq头部,默认选项

    3: 将头节点插入到cxq尾部

当锁空闲的时候,根据不同的策略,以不同的方式将_cxq插入到_EntryList

QMode = 2且cxq非空:取cxq队列队首的ObjectWaiter对象,调用ExitEpilog方法,该方法会唤醒ObjectWaiter对象的线程,然后立即返回,后面的代码不会执行了;

QMode = 3且cxq非空:把cxq队列插入到EntryList的尾部;

QMode = 4且cxq非空:把cxq队列插入到EntryList的头部;

QMode = 0:暂时什么都不做

  • 从队列中取线程来进入临界区:
    如果EntryList的首元素非空,就取出来调用ExitEpilog方法,该方法会唤醒ObjectWaiter对象的线程,然后立即返回;
    如果EntryList的首元素为空,就将cxq的所有元素放入到EntryList中,然后再从EntryList中取出来队首元素执行ExitEpilog方法,然后立即返回;
  • 如果是重入则会_recursions++

总结

  • 偏向锁适用于只有一个线程会进入临界资源的情景,只要有多线程进入临界区就会导致锁升级
  • 轻量级锁适合多线程交替进入临界区的情景,只要有多线程同时想进入临界区就会导致锁升级
  • 偏向锁和轻量级锁的重入都是由线程中的锁记录来实现的
  • 重量级锁采用Moniter实现。根据不同策略和操作,线程在 _WaitSet _cxq _EntryList 中流转,重入通过_recursions计数来实现。

java锁synchronized源码分析并发编程

阅读 6发布于 今天 09:53

本作品系原创,采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议

avatar

*-*Qw6IX

1 声望

0 粉丝

0 条评论

得票时间

avatar

*-*Qw6IX

1 声望

0 粉丝

宣传栏

synchronized介绍

synchronized相信大家都比较熟悉,面试的时候也是必备问题。有很多书籍介绍它,网上也有很多很多相关的文字,我自己也看过很多。但是看了就忘,印象不深,于是我决定阅读源码来加深记忆。代码出真知,这一看不要紧,不仅让我对synchronized有了更深入的理解,我还发现,网上许多文章,甚至很多面试官的理解都是错误的。

比如,说说你对自旋锁的理解。你可以先把答案自己在心里想一下,看到下面,你可能会发现,原来你原来的理解是错误的。

synchronized使用

使用上我就不多说了,相信大家都知道,这里概括一下

synchronized(obj) {

....

some code

...

}

这种使用时对obj对象上锁。也是比较常见的使用。

synchronized void test() {

....

some code

...

}

这种事对this进行上锁

synchronized static void test() {

....

some code

...

}

这种事对Class对象上锁。

synchronized字节码表示

加入我们有一个简单的方法如下

public void test() {

Object obj = new Object();

synchronized (obj) {

System.out.println("in locking");

}

}

会编译成以下字节码

 public test()V

...

MONITORENTER

L0

LINENUMBER 15 L0

GETSTATIC java/lang/System.out : Ljava/io/PrintStream;

LDC "in locking"

INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V

L6

LINENUMBER 16 L6

ALOAD 2

MONITOREXIT

L1

GOTO L7

L2

FRAME FULL [com/pinduoduo/service/jmm/JmmTest java/lang/Object java/lang/Object] [java/lang/Throwable]

ASTORE 3

ALOAD 2

MONITOREXIT

...

}

其他我们不要管,可以看到字节码中有一个MONITOREXIT指令和两个MONITOREXIT指令。两个MONITOREXIT是因为要异常的情况也也要进行MONITOREXIT.

synchronized的jvm表示

我们在上小节知道了synchronized块是有MONITORENTERMONITOREXIT指令包裹着的,那这两个指令怎么执行呢?这就涉及的jvm怎么实现的了,这里我们就以hotspot为例,看看他是怎么实现的。

hotspot的这两个指令执行入口在bytecodeInterpreter.cpp中的CASE(_monitorenter)CASE(_monitorexit)中,接着就是一堆c++代码了,这里我们不深入,不然大家肯定睡着了,有兴趣的小伙伴可以自己去看看,我其他文章也会具体分析。

synchronized原理

这一小节会简单的说一说synchronized的原理,都是根据我阅读源码的总结。

偏向锁

偏向锁适用于没有多个线程会进入的资源。这个点在说轻量级锁的时候会再次提到。

偏向锁又称无锁,因为它只改变锁对象头的markWord。

结构图如下:
【Java】synchronized详解

偏向锁状态下,markword的锁标志位会变成01,是否偏向标志为1,此外epoch只一个标志,用于是否需要批量重定向的判断,我们先不管它。那如何证明偏向锁状态下,锁的markword会变成这个样子呢?我们的jol就登场了,跟我一起看接下来的小demo,耐心。

首先我们引入jol的依赖

 <dependency>

<groupId>org.openjdk.jol</groupId>

<artifactId>jol-core</artifactId>

<version>0.9</version>

</dependency>

代码如下

import org.openjdk.jol.info.ClassLayout;

import org.openjdk.jol.vm.VM;

public class MarkWordTest {

public static void main(String[] args) throws InterruptedException {

try {

Thread.sleep(5000);

} catch (InterruptedException e) {

e.printStackTrace();

}

MarkWordTest object = new MarkWordTest();

String classLayout1 = ClassLayout.parseInstance(object).toPrintable();

System.out.println("---------------------------加锁前---------------------------");

System.out.println(classLayout1);

System.out.println("------------------------------------------------------------");

synchronized (object) {

//打印当前jvm信息

System.out.println("---------------------------加锁中---------------------------");

String classLayout2 = ClassLayout.parseInstance(object).toPrintable();

System.out.println(classLayout2);

System.out.println("------------------------------------------------------------");

}

System.out.println("---------------------------加锁后---------------------------");

String classLayout3 = ClassLayout.parseInstance(object).toPrintable();

System.out.println(classLayout3);

System.out.println("------------------------------------------------------------");

}

}

首先注意到,代码开始有一个sleep 5秒的操作,是因为偏向锁默认有个延迟启动时间,默认为4s。

然后分别在加锁前、中、后对对象头进行打印,我们看一下打印结果

---------------------------加锁前---------------------------

com.pinduoduo.service.jmm.MarkWordTest object internals:

OFFSET SIZE TYPE DESCRIPTION VALUE

0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)

4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)

8 4 (object header) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)

12 4 (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

------------------------------------------------------------

---------------------------加锁中---------------------------

com.pinduoduo.service.jmm.MarkWordTest object internals:

OFFSET SIZE TYPE DESCRIPTION VALUE

0 4 (object header) 05 38 67 03 (00000101 00111000 01100111 00000011) (57096197)

4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)

8 4 (object header) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)

12 4 (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

------------------------------------------------------------

---------------------------加锁后---------------------------

com.pinduoduo.service.jmm.MarkWordTest object internals:

OFFSET SIZE TYPE DESCRIPTION VALUE

0 4 (object header) 05 38 67 03 (00000101 00111000 01100111 00000011) (57096197)

4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)

8 4 (object header) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)

12 4 (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

------------------------------------------------------------

Process finished with exit code 0

markword是64位,既上面一堆数字的前两行,我们删掉其他东西,只看markword

---------------------------加锁前---------------------------

...

0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)

4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)

...

------------------------------------------------------------

---------------------------加锁中---------------------------

...

0 4 (object header) 05 38 67 03 (00000101 00111000 01100111 00000011) (57096197)

4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)

...

------------------------------------------------------------

---------------------------加锁后---------------------------

...

0 4 (object header) 05 38 67 03 (00000101 00111000 01100111 00000011) (57096197)

4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)

...

------------------------------------------------------------

是不是有点看不懂,这是因为,Hopspot是小端储存,既低位值放在高位地址上。那我们按照我们好读的宗旨把它改为大端存

---------------------------加锁前---------------------------

00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101

------------------------------------------------------------

---------------------------加锁中---------------------------

00000000 00000000 00000000 00000000 00000011 01100111 00111000 00000101

------------------------------------------------------------

---------------------------加锁后---------------------------

00000000 00000000 00000000 00000000 00000011 01100111 00111000 00000101

------------------------------------------------------------

现在就是我们可以读懂的结构了。

等等,加锁前为啥锁标志位为01,偏向锁模式呢?是的,是这样的,对象头初始就是偏向锁模式,并不是很多文章都说的无锁模式,只是现在还没有指向任何线程。

加锁中我们就好理解了,锁标志位是偏向锁,并且线程id也不是0了。

加锁后可以看到,markword并没有任何改变。这样做自然有用,就是,当有其他线程来想进入临界区的时候,看到偏向线程id不是自己,直接进行锁升级(不考虑批量重偏向)。

至此我们可以得出一个结论:偏向锁只在只有一个线程会进入临界区的场景有用。

不知道你有没有注意到一个问题,就是,既然退出临界区后,markword没有任何变化,那锁重入的计数怎么实现的?这就涉及到synchronized的重入了,不管是偏向锁、轻量锁还是重量级锁,都是使用的同一套重入机制。

自旋锁的重入

在Hopspot中,每个线程都有一个锁记录表,锁记录是BasicObjectLock类型,里边有一个obj和displaced_header,obj存储锁对象,set_displaced_header锁对象的markword,每一次进入临界区,都会创建一个BasicObjectLock对象,如果是重复,那displaced_header就置为null.

【Java】synchronized详解

轻量级锁

轻量级锁适用于线程交替进入临界区的场景,只要有锁竞争就会升级成重量级锁。注意,1.8中<font color=red>没有自旋锁</font>,别再说经过自旋后升级成重量级锁!!!

别嫌我啰嗦,再比较一下偏向锁与轻量级锁适用场景的不同。

偏向锁:只有一个线程会进入临界区

轻量级锁:不同线程交替进入临界区

偏向锁和轻量级锁都是在没有锁竞争的条件下适用的。

轻量级锁的加锁和释放都是通过cas操作对锁对象的markword就行修改而实现的,它的重入与偏向锁的机制相同,这里就不赘述了。

重量级锁

重量级锁的实现是用了监视器模式(moniter),我猜字节码指令MONITORENTERMONITOREXIT开始指的就是这个操作,后来经过一系列的优化,使这两个指令的字面意义跟锁的实现不太对应了。

在Hotspot中,每个对象(java对象)都会关联一个ObjectMonitor,这个就是一个监视器。

ObjectMonitor中我们重点关注以下几个字段

 ObjectMonitor() {

_owner = NULL; // 锁持有者

_recursions = 0; // 重入次数

_WaitSet = NULL;

_cxq = NULL ;

_EntryList = NULL ;

}

_owner既为锁持有者。

_recursions表示重入次数。从这个字段就可以看出来,重量级锁的重入跟轻量级锁、偏向锁的重入不同,是用计数法来做的。

_cxq是一个队列,如果线程获取锁失败,一定是进入这个队列的。

_EntryList也是一个人队列,当ObjectMonitor持有者释放锁后,ObjectMonitor一定是从这个队列中唤醒一个线程的。

_WaitSet是调用wait操作的线程队列。

认识这几个字段后,我们会很容易理解ObjectMonitor的运作原来,并没有网上说的那么复杂,至于网上经常晒的那张流程图我就不贴了。下面我概括一下。

  • 线程要进入临界区的时候,如果_owner为空,则直接用cas去修改_owner的值,成功则证明获取锁成功,否则,会准备进去队列,在进入队列前,会经过多次自旋尝试cas修改_owner,这也是为什么synchronized,是非公平锁的原因,就是他会不断的抢占式的获取锁。
  • 多次尝试无果后,线程会进入_cxq队列中。注意,抢占无果的线程一定是进入_cxq队列。
  • 线程在获取锁后,如果调用lock#wait方法,那这个线程会进入_WaitSet队列。
  • 当获取锁的线程调notify或notifyAll时,根据不同的策略,_WaitSet会将节点插入_cxq_EntryList中。

    0: 将头节点插入到EntryList头部

    1: 将头节点插入到EntryList尾部

    2: 将头节点插入到cxq头部,默认选项

    3: 将头节点插入到cxq尾部

当锁空闲的时候,根据不同的策略,以不同的方式将_cxq插入到_EntryList

QMode = 2且cxq非空:取cxq队列队首的ObjectWaiter对象,调用ExitEpilog方法,该方法会唤醒ObjectWaiter对象的线程,然后立即返回,后面的代码不会执行了;

QMode = 3且cxq非空:把cxq队列插入到EntryList的尾部;

QMode = 4且cxq非空:把cxq队列插入到EntryList的头部;

QMode = 0:暂时什么都不做

  • 从队列中取线程来进入临界区:
    如果EntryList的首元素非空,就取出来调用ExitEpilog方法,该方法会唤醒ObjectWaiter对象的线程,然后立即返回;
    如果EntryList的首元素为空,就将cxq的所有元素放入到EntryList中,然后再从EntryList中取出来队首元素执行ExitEpilog方法,然后立即返回;
  • 如果是重入则会_recursions++

总结

  • 偏向锁适用于只有一个线程会进入临界资源的情景,只要有多线程进入临界区就会导致锁升级
  • 轻量级锁适合多线程交替进入临界区的情景,只要有多线程同时想进入临界区就会导致锁升级
  • 偏向锁和轻量级锁的重入都是由线程中的锁记录来实现的
  • 重量级锁采用Moniter实现。根据不同策略和操作,线程在 _WaitSet _cxq _EntryList 中流转,重入通过_recursions计数来实现。

以上是 【Java】synchronized详解 的全部内容, 来源链接: utcz.com/a/111172.html

回到顶部