前言
毫无疑问,synchronized是我们用过的第一个并发关键字,很多博文都在讲解这个技术。不过大多数讲解还停留在对synchronized的使用层面,其底层的很多原理和优化,很多人可能并不知晓。 因此本文将通过对synchronized的大量C源码分析,让大家对他的了解更加透彻点。
本篇将从为什么要引入synchronized,常见的使用方式,存在的问题以及优化部分这四个方面描述,话不多说,开始表演。

可见性问题及解决
概念描述
指一个线程对共享变量进行修改,另一个能立刻获取到修改后的最新值。
代码展示
类:
public
class
Example1
{
//1.创建共享变量
private
static boolean flag
=
true
;
public
static
void
main
(String
[
] args
) throws Exception
{
//2.t1空循环,如果flag为true,不退出
Thread t1
=
new
Thread
(
new
Runnable
(
)
{
@Override
public
void
run
(
)
{
while
(
true
)
{
if
(
!flag
)
{
System
.out
.
println
(
"进入if"
)
;
break
;
}
}
}
}
)
;
t1
.
start
(
)
;
Thread
.
sleep
(
2000L
)
;
//2.t2修改flag为false
Thread t2
=
new
Thread
(
new
Runnable
(
)
{
@Override
public
void
run
(
)
{
flag
=
false
;
System
.out
.
println
(
"修改了"
)
;
}
}
)
;
t2
.
start
(
)
;
}
}
运行结果:

分析
这边先要了解下Java的内存模式,不明白的可点击传送门,todo。
下图线程t1,t2从主内存分别获取flag=true,t1空循环,直到flag为false的时候退出循环。t2拿到flag的值,将其改为false,并写入到主内存。此时主内存和线程t2的工作内存中flag均为false,但是线程t1工作内存中的flag还是true,所以一直退不了循环,程序将一直执行。

synchronized如何解决可见性
首先我们尝试在t1线程中加一行打印语句,看看效果。
代码:
public
class
Example1
{
//1.创建共享变量
private
static boolean flag
=
true
;
public
static
void
main
(String
[
] args
) throws Exception
{
//2.t1空循环,如果flag为true,不退出
Thread t1
=
new
Thread
(
new
Runnable
(
)
{
@Override
public
void
run
(
)
{
while
(
true
)
{
//新增的打印语句
System
.out
.
println
(flag
)
;
if
(
!flag
)
{
System
.out
.
println
(
"进入if"
)
;
break
;
}
}
}
}
)
;
t1
.
start
(
)
;
Thread
.
sleep
(
2000L
)
;
//2.t2修改flag为false
Thread t2
=
new
Thread
(
new
Runnable
(
)
{
@Override
public
void
run
(
)
{
flag
=
false
;
System
.out
.
println
(
"修改了"
)
;
}
}
)
;
t2
.
start
(
)
;
}
}
运行结果:

我们发现if里面的语句已经打印出来了,线程1已经感知到线程2对flag的修改,即这条打印语句已经影响了可见性。这是为啥?

答案就是println中,我们看下源码:

println有个上锁的过程,即操作如下:
1.获取同步锁。
2.清空自己工作内存上的变量。
3.从主内存获取最新值,并加载到工作内存中。
4.打印并输出。
所以这里解释了为什么线程t1加了打印语句之后,t1立刻能感知t2对flag的修改。因为每次打印的时候其都从主内存上获取了最新值,当t2修改的时候,t1立刻从主内存获取了值,所以进入了if语句,并最终能跳出循环。
synchronized的原理就是清空自己工作内存上的值,通过将主内存最新值刷新到工作内存中,让各个线程能互相感知修改。
原子性问题及解决
概念描述
在一次或多个操作中,要不所有操作都执行,要不所有操作都不执行。
代码展示
类:
public
class
Example2
{
//1.定义全局变量number
private
static int number
=
0
;
public
static
void
main
(String
[
] args
) throws Exception
{
Runnable runnable
=
(
)
-
>
{
for
(int i
=
0
; i
<
10000
; i
++
)
{
number
++
;
}
}
;
//2.t1让其自增10000
Thread t1
=
new
Thread
(runnable
)
;
t1
.
start
(
)
;
//3.t2让其自增10000
Thread t2
=
new
Thread
(runnable
)
;
t2
.
start
(
)
;
//4.等待t1,t2运行结束
t1
.
join
(
)
;
t2
.
join
(
)
;
System
.out
.
println
(
"number="
+ number
)
;
}
}
运行结果:

分析
每个线程执行的逻辑是循环1万次,每次加1,那我们希望的结果是2万,但是实际上结果是不足2万的。我们先用javap命令反汇编,我们看到很多代码,但是number++涉及的指令有四句,具体看第二张图。


如果有多条线程执行这段number++代码,当前number为0,线程1先执行到iconst_1指令,即将执行iadd操作,而线程2执行到getstatic指令,这个时候number值还没有改变,所以线程2获取到的静态字段是0,线程1执行完iadd操作,number变为1,线程2执行完iadd操作,number还是1。这个时候就发现问题了,做了两次number++操作,但是number只增加了1。
并发编程时,会出现原子性问题,当一个线程对共享变量操作到一半的时候,另外一个线程也有可能来操作共享变量,这个时候就出现了问题。
synchronized如何解决原子性问题
在上面的分析中,我们已经知道发生问题的原因,number++是由四条指令组成,没有保证原子操作。所以,我们只要将number++作为一个整体就行,即保证他的原子性。具体代码如下:
public
class
Example2
{
//1.定义全局变量number
private
static int number
=
0
;
//新增一个静态变量object
private
static Object object
=
new
Object
(
)
;
public
static
void
main
(String
[
] args
) throws Exception
{
Runnable runnable
=
(
)
-
>
{
for
(int i
=
0
; i
<
10000
; i
++
)
{
//将number++的操作用object对象锁住
synchronized
(
object
)
{
number
++
;
}
}
}
;
//2.t1让其自增10000
Thread t1
=
new
Thread
(runnable
)
;
t1
.
start
(
)
;
//3.t2让其自增10000
Thread t2
=
new
Thread
(runnable
)
;
t2
.
start
(
)
;
//4.等待t1,t2运行结束
t1
.
join
(
)
;
t2
.
join
(
)
;
System
.out
.
println
(
"number="
+ number
)
;
}
}

我们看到最终number为20000,那为什么要加上synchronized,结果就正确了?我们再反编译下Example2,可以看到在四行指令前后分别有monitorenter和monitorexist,线程1在执行中间指令时,其他线程不可以进入monitorenter,需要等线程1执行完monitorexist,其他进程才能继续monitorenter,进行自增操作。
//java学习交流:737251827 进入可领取学习资源及对十年开发经验大佬提问,免费解答!
有序性问题及解决
概念描述
代码中程序执行的顺序,Java在编译和运行时会对代码进行优化,这样会导致我们最终的执行顺序并不是我们编写代码的书写顺序。
代码展示
咱先来看一个概念,
重排序,也就是语句的执行顺序会被重新安排。其主要分为三种:
1.编译器优化的重排序:可以重新安排语句的执行顺序。
2.指令级并行的重排序:现代处理器采用指令级并行技术,将多条指令重叠执行。
3.内存系统的重排序:由于处理器使用缓存和读写缓冲区,所以看上去可能是乱序的。
上面代码中的a = new A();可能被被JVM分解成如下代码:
// 可以分解为以下三个步骤
1 memory
=
allocate
(
)
;
// 分配内存 相当于c的malloc
2
ctorInstanc
(memory
)
//初始化对象
3 s
=memory
//设置s指向刚分配的地址
// 上述三个步骤可能会被重排序为 1-3-2,也就是:
1 memory
=
allocate
(
)
;
// 分配内存 相当于c的malloc
3 s
=memory
//设置s指向刚分配的地址
2
ctorInstanc
(memory
)
//初始化对象
一旦假设发生了这样的重排序,比如线程A在执行了步骤1和步骤3,但是步骤2还没有执行完。这个时候线程B进入了第一个语句,它会判断a不为空,即直接返回了a。其实这是一个未初始化完成的a,即会出现问题。
synchronized如何解决有序性问题
给上面的三个步骤加上一个synchronized关键字,即使发生重排序也不会出现问题。线程A在执行步骤1和步骤3时,线程B因为没法获取到锁,所以也不能进入第一个语句。只有线程A都执行完,释放锁,线程B才能重新获取锁,再执行相关操作。
synchronized的常见使用方式
修饰代码块(同步代码块)
synchronized
(
object
)
{
//具体代码
}
修饰方法
synchronized
void
test
(
)
{
//具体代码
}
synchronized不能继承?(插曲)
父类A:
public
class
A
{
synchronized
void
test
(
) throws Exception
{
try
{
System
.out
.
println
(
"main 下一步 sleep begin threadName="
+ Thread
.
currentThread
(
)
.
getName
(
)
+
" time="
+ System
.
currentTimeMillis
(
)
)
;
Thread
.
sleep
(
5000
)
;
System
.out
.
println
(
"main 下一步 sleep end threadName="
+ Thread
.
currentThread
(
)
.
getName
(
)
+
" time="
+ System
.
currentTimeMillis
(
)
)
;
}
catch
(Exception e
)
{
e
.
printStackTrace
(
)
;
}
}
}
子类B:(未重写test方法)
public
class
B
extends
A
{
}
子类C:(重写test方法)
public
class
C
extends
A
{
@Override
void
test
(
) throws Exception
{
try
{
System
.out
.
println
(
"sub 下一步 sleep begin threadName="
+ Thread
.
currentThread
(
)
.
getName
(
)
+
" time="
+ System
.
currentTimeMillis
(
)
)
;
Thread
.
sleep
(
5000
)
;
System
.out
.
println
(
"sub 下一步 sleep end threadName="
+ Thread
.
currentThread
(
)
.
getName
(
)
+
" time="
+ System
.
currentTimeMillis
(
)
)
;
}
catch
(Exception e
)
{
e
.
printStackTrace
(
)
;
}
}
}
线程A:
public
class
ThreadA
extends
Thread
{
private
A a
;
public
void
setter
(
A a
)
{
this
.a
= a
;
}
@Override
public
void
run
(
)
{
try
{
a
.
test
(
)
;
}
catch
(Exception e
)
{
}
}
}
线程B:
public
class
ThreadB
extends
Thread
{
private
B b
;
public
void
setB
(
B b
)
{
this
.b
=b
;
}
@Override
public
void
run
(
)
{
try
{
b
.
test
(
)
;
}
catch
(Exception e
)
{
}
}
}
线程C:
public
class
ThreadC
extends
Thread
{
private
C c
;
public
void
setC
(
C c
)
{
this
.c
=c
;
}
@Override
public
void
run
(
)
{
try
{
c
.
test
(
)
;
}
catch
(Exception e
)
{
}
}
}
测试类test:
public
class
test
{
public
static
void
main
(String
[
] args
) throws Exception
{
A a
=
new
A
(
)
;
ThreadA
A1
=
new
ThreadA
(
)
;
A1
.
setter
(a
)
;
A1
.
setName
(
"A1"
)
;
A1
.
start
(
)
;
ThreadA
A2
=
new
ThreadA
(
)
;
A2
.
setter
(a
)
;
A2
.
setName
(
"A2"
)
;
A2
.
start
(
)
;
A1
.
join
(
)
;
A2
.
join
(
)
;
System
.out
.
println
(
"============="
)
;
B b
=
new
B
(
)
;
ThreadB
B1
=
new
ThreadB
(
)
;
B1
.
setB
(b
)
;
B1
.
setName
(
"B1"
)
;
B1
.
start
(
)
;
ThreadB
B2
=
new
ThreadB
(
)
;
B2
.
setB
(b
)
;
B2
.
setName
(
"B2"
)
;
B2
.
start
(
)
;
B1
.
join
(
)
;
B2
.
join
(
)
;
System
.out
.
println
(
"============="
)
;
C c
=
new
C
(
)
;
ThreadC
C1
=
new
ThreadC
(
)
;
C1
.
setName
(
"C1"
)
;
C1
.
setC
(c
)
;
C1
.
start
(
)
;
ThreadC
C2
=
new
ThreadC
(
)
;
C2
.
setName
(
"C2"
)
;
C2
.
setC
(c
)
;
C2
.
start
(
)
;
C1
.
join
(
)
;
C2
.
join
(
)
;
}
}
运行结果:

子类B继承了父类A,但是没有重写test方法,ThreadB仍然是同步的。子类C继承了父类A,也重写了test方法,但是未明确写上synchronized,所以这个方法并不是同步方法。只有显式的写上synchronized关键字,才是同步方法。
所以synchronized不能继承这句话有歧义,我们只要记住子类如果想要重写父类的同步方法,synchronized关键字一定要显示写出,否则无效。
修饰静态方法
synchronized
static
void
test
(
)
{
//具体代码
}
修饰类
synchronized
(
Example2
.class
)
{
//具体代码
}
Java对象 Mark Word
在JVM中,对象在内存中的布局分为三块区域:对象头,实例数据和对齐数据,如下图:

其中Mark Word值在
不同锁状态下的展示如下:(重点看线程id,是否为偏向锁,锁标志位信息)

在64位系统中,Mark Word占了8个字节,类型指针占了8个字节,一共是
16个字节。Talk is cheap. Show me the code. 咱来看代码。
-
我们想要看Java对象的Mark Word,先要加载一个jar包,在pom.xml添加即可。
<dependency
>
<groupId
>org
.openjdk
.jol
<
/groupId
>
<artifactId
>jol
-core
<
/artifactId
>
<version
>
0.9
<
/version
>
<
/dependency
>
-
新建一个对象A,拥有初始值为666的变量x。
public
class
A
{
private int x
=
666
;
}
-
新建一个测试类test,这涉及到刚才加载的jar,我们打印Java对象。
import org
.openjdk
.jol
.info
.ClassLayout
;
public
class
test
{
public
static
void
main
(
String
[
] args
)
{
A a
=
new
A
(
)
;
System
.out
.
println
( ClassLayout
.
parseInstance
(a
)
.
toPrintable
(
)
)
;
}
}
-
我们发现对象头(object header)占了
12个字节,为啥和上面说的16个字节不一样。

-
其实上是默认开启了指针压缩,我们需要关闭指针压缩,也就是添加
-
配置。
-XX:-UseCompressedOops

-
再次执行,发现对象头为16个字节。

偏向锁
什么是偏向锁
JDK1.6之前锁为重量级锁(待会说,只要知道他和内核交互,消耗资源),1.6之后Java设计人员发现很多情况下并不存在多个线程竞争的关系,所以为了资源问题引入了
无锁,
偏向锁,
轻量级锁,
重量级锁的概念。先说偏向锁,他是偏心,偏袒的意思,这个锁会偏向于第一个获取他的线程。
偏向锁演示
-
创建并启动一个线程,run方法里面用了synchronized关键字,功能是打印this的Java对象。
public
class
test
{
public
static
void
main
(
String
[
] args
)
{
Thread thread
=
new
Thread
(
new
Runnable
(
)
{
@Override
public
void
run
(
)
{
synchronized
(
this
)
{
System
.out
.
println
(ClassLayout
.
parseInstance
(
this
)
.
toPrintable
(
)
)
;
}
}
}
)
;
thread
.
start
(
)
;
}
}
标红的地方为000,根据之前Mark Word在不同状态下的标志,得此为无锁状态。理论上一个线程使用synchronized关键字,应为偏向锁。

-
实际上偏向锁在JDK1.6之后是默认开启的,但是启动时间有延迟,所以需要添加参数
-XX:BiasedLockingStartupDelay=0,让其在程序启动时立刻启动。

-
重新运行下代码,发现标红地方101,对比Mark Word在不同状态下的标志,得此状态为偏向锁。
//java学习交流:737251827 进入可领取学习资源及对十年开发经验大佬提问,免费解答!
偏向锁原理图解
-
在线程的run方法中,刚执行到synchronized,会判断当前对象是否为偏向锁和锁标志,没有任何线程执行该对象,我们可以看到是否为偏向锁为0,锁标志位01,即无锁状态。
-
线程会将自己的id赋值给markword,即将原来的hashcode值改为线程id,是否是偏向锁改为1,表示线程拥有对象锁,可以执行下面的业务逻辑。
如果synchronized执行完,对象还是偏向锁状态;如果线程结束之后,会撤销偏向锁,将该对象还原成无锁状态。 -
如果同一个线程中又对该对象进行加锁操作,我们只要对比
对象的线程id是否与线程id相同,如果相同即为线程锁重入问题。
优势
加锁和解锁不需要额外的消耗,和执行非同步方法相比只有纳秒级的差距。
白话翻译
线程1锁定对象this,他发现对象为无锁状态,所以将线程id赋值给对象的Mark Word字段,表示对象为线程1专用,即使他退出了同步代码,其他线程也不能使用该对象。
同学A去自习教室C,他发现教室无人,所以在门口写了个名字,表示当前教室有人在使用,这样即使他出去吃了饭,其他同学也不能使用这个房间。
轻量锁
什么是轻量级锁
在多线程交替同步代码块的情况下,线程间没有竞争,使用轻量级锁可以避免重量级锁引入的性能消耗。
轻量级图解
-
在刚才偏向锁的基础上,如果有另外一个线程也想错峰使用该资源,通过对比线程id是否相同,Java内存会立刻撤销偏向锁(需要等待全局安全点),进行锁升级的操作。
-
撤销完轻量级锁,会在线程1的方法栈中新增一个锁记录,对象的Mark Word与锁记录交换。
优势
线程不竞争的时候,避免直接使用重量级锁,提高了程序的响应速度。
白话翻译
在刚才偏向锁的基础上,另外一个线程也想要获取资源,所以线程1需要撤销偏向锁,升级为轻量锁。
同学A在使用自习教室外面写了自己的名字,所以同学B来也想要使用自习教室,他需要提醒同学A,不能使用偏向锁,同学A将自习教室门口的名字擦掉,换成了一个书包,里面是自己的书籍。这样在同学A不使用自习教室的时候,同学B也能使用自习教室,只需要将自己的书包也挂在外面即可。这样下次来使用的同学就能知道已经有人占用了该教室。
重量级锁
什么是重量级锁
当多线程之间发生竞争,Java内存会申请一个Monitor对象来实现。
重量级锁原理图解
在刚才的轻量级锁的基础上,线程2也想要申请资源,发现锁的标志位为00,即为轻量级锁,所以向内存申请一个Monitor,让对象的MarkWord指向Monitor地址,并将ower指针指向线程1的地址,线程2放在等待队列里面,等线程1指向完毕,释放锁资源。

Monitor源码分析
环境搭建
我们去官网 找下open源码,也可以通过其他途径下载。源码是C实现的,可以通过DEV C++工具打开,效果如下图:

构造函数
我们先看下
\hotspot\src\share\vm\runtime\ObjectMonitor.hpp,以.hpp结尾的文件是导入的一些包和一些声明,之后可以被.cpp文件导入。
ObjectMonitor
(
)
{
_header
=
NULL
;
_count
=
0
;
_waiters
=
0
,
_recursions
=
0
;
//线程重入次数
_object
=
NULL
;
//存储该monitor的对象
_owner
=
NULL
;
//标识拥有该monitor的线程
_WaitSet
=
NULL
;
//处于wait状态的线程,会加入到_waitSet
_WaitSetLock
=
0
;
_Responsible
=
NULL
;
_succ
=
NULL
;
_cxq
=
NULL
;
//多线程竞争锁时的单项列表
FreeNext
=
NULL
;
_EntryList
=
NULL
;
//处于等待锁lock状态的线程,会被加入到该列表
_SpinFreq
=
0
;
_SpinClock
=
0
;
OwnerIsThread
=
0
;
_previous_owner_tid
=
0
;
}
锁竞争的过程
我们先看下
\hotspot\src\share\vm\interpreter\interpreterRuntime.cpp,
IRT_ENTRY_NO_ASYNC即为锁竞争过程。
//%note monitor_1
IRT_ENTRY_NO_ASYNC
(
void
, InterpreterRuntime
:
:
monitorenter
(JavaThread
* thread
, BasicObjectLock
* elem
)
)
#ifdef
ASSERT
thread
-
>
last_frame
(
)
.
interpreter_frame_verify_monitor
(elem
)
;
#endif
if
(PrintBiasedLockingStatistics
)
{
Atomic
:
:
inc
(BiasedLocking
:
:
slow_path_entry_count_addr
(
)
)
;
}
Handle
h_obj
(thread
, elem
-
>
obj
(
)
)
;
assert
(Universe
:
:
heap
(
)
-
>
is_in_reserved_or_null
(
h_obj
(
)
)
,
"must be NULL or an object"
)
;
//是否使用偏向锁,可加参数进行设置 if (UseBiasedLocking) { //如果可以使用偏向锁,即进入fast_enter
// Retry fast entry if bias is revoked to avoid unnecessary inflation
ObjectSynchronizer
:
:
fast_enter
(h_obj
, elem
-
>
lock
(
)
,
true
,
CHECK
)
;
}
else
{
//如果不可以使用偏向锁,即进行slow_enter
ObjectSynchronizer
:
:
slow_enter
(h_obj
, elem
-
>
lock
(
)
,
CHECK
)
;
}
assert
(Universe
:
:
heap
(
)
-
>
is_in_reserved_or_null
(elem
-
>
obj
(
)
)
,
"must be NULL or an object"
)
;
#ifdef
ASSERT
thread
-
>
last_frame
(
)
.
interpreter_frame_verify_monitor
(elem
)
;
#endif
slow_enter实际上调用的ObjectMonitor.cpp的enter 方法
void
ATTR ObjectMonitor
:
:
enter
(
TRAPS
)
{
// The following code is ordered to check the most common cases first
// and to reduce RTS->RTO cache line upgrades on SPARC and IA32 processors.
Thread
*
const Self
=
THREAD
;
void
* cur
;
//通过CAS操作尝试将monitor的_owner设置为当前线程
cur
= Atomic
:
:
cmpxchg_ptr
(Self
,
&_owner
,
NULL
)
;
//如果设置不成功,直接返回
if
(
cur
==
NULL
)
{
// Either ASSERT _recursions == 0 or explicitly set _recursions = 0.
assert
(_recursions
==
0
,
"invariant"
)
;
assert
(_owner
== Self
,
"invariant"
)
;
// CONSIDER: set or assert OwnerIsThread == 1
return
;
}
//如果_owner等于当前线程,重入数_recursions加1,直接返回
if
(
cur
== Self
)
{
// TODO-FIXME: check for integer overflow! BUGID 6557169.
_recursions
++
;
return
;
}
//如果当前线程第一次进入该monitor,设置重入数_recursions为1,_owner为当前线程,返回
if
(Self
-
>
is_lock_owned
(
(address
)cur
)
)
{
assert
(_recursions
==
0
,
"internal state error"
)
;
_recursions
=
1
;
// Commute owner from a thread-specific on-stack BasicLockObject address to
// a full-fledged "Thread *".
_owner
= Self
;
OwnerIsThread
=
1
;
return
;
}
//如果未抢到锁,则进行自旋优化,如果还未获取锁,则放入到list里面
// We've encountered genuine contention.
assert
(Self
-
>_Stalled
==
0
,
"invariant"
)
;
Self
-
>_Stalled
=
intptr_t
(
this
)
;
// Try one round of spinning *before* enqueueing Self
// and before going through the awkward and expensive state
// transitions. The following spin is strictly optional ...
// Note that if we acquire the monitor from an initial spin
// we forgo posting JVMTI events and firing DTRACE probes.
if
(
Knob_SpinEarly
&&
TrySpin
(Self
)
>
0
)
{
assert
(_owner
== Self
,
"invariant"
)
;
assert
(_recursions
==
0
,
"invariant"
)
;
assert
(
(
(oop
)
(
object
(
)
)
)
-
>
mark
(
)
== markOopDesc
:
:
encode
(
this
)
,
"invariant"
)
;
Self
-
>_Stalled
=
0
;
return
;
}
assert
(_owner
!= Self
,
"invariant"
)
;
assert
(_succ
!= Self
,
"invariant"
)
;
assert
(Self
-
>
is_Java_thread
(
)
,
"invariant"
)
;
JavaThread
* jt
=
(JavaThread
*
) Self
;
assert
(
!SafepointSynchronize
:
:
is_at_safepoint
(
)
,
"invariant"
)
;
assert
(jt
-
>
thread_state
(
)
!= _thread_blocked
,
"invariant"
)
;
assert
(
this
-
>
object
(
)
!=
NULL
,
"invariant"
)
;
assert
(_count
>=
0
,
"invariant"
)
;
// Prevent deflation at STW-time. See deflate_idle_monitors() and is_busy().
// Ensure the object-monitor relationship remains stable while there's contention.
Atomic
:
:
inc_ptr
(
&_count
)
;
EventJavaMonitorEnter event
;
{
// Change java thread status to indicate blocked on monitor enter.
JavaThreadBlockedOnMonitorEnterState
jtbmes
(jt
,
this
)
;
DTRACE_MONITOR_PROBE
(contended__enter
,
this
,
object
(
)
, jt
)
;
if
(
JvmtiExport
:
:
should_post_monitor_contended_enter
(
)
)
{
JvmtiExport
:
:
post_monitor_contended_enter
(jt
,
this
)
;
}
OSThreadContendState
osts
(Self
-
>
osthread
(
)
)
;
ThreadBlockInVM
tbivm
(jt
)
;
Self
-
>
set_current_pending_monitor
(
this
)
;
// TODO-FIXME: change the following for(;;) loop to straight-line code.
for
(
;
;
)
{
jt
-
>
set_suspend_equivalent
(
)
;
// cleared by handle_special_suspend_equivalent_condition()
// or java_suspend_self()
EnterI
(
THREAD
)
;
if
(
!
ExitSuspendEquivalent
(jt
)
)
break
;
//
// We have acquired the contended monitor, but while we were
// waiting another thread suspended us. We don't want to enter
// the monitor while suspended because that would surprise the
// thread that suspended us.
//
_recursions
=
0
;
_succ
=
NULL
;
exit
(
false
, Self
)
;
jt
-
>
java_suspend_self
(
)
;
}
Self
-
>
set_current_pending_monitor
(
NULL
)
;
}
Atomic
:
:
dec_ptr
(
&_count
)
;
assert
(_count
>=
0
,
"invariant"
)
;
Self
-
>_Stalled
=
0
;
// Must either set _recursions = 0 or ASSERT _recursions == 0.
assert
(_recursions
==
0
,
"invariant"
)
;
assert
(_owner
== Self
,
"invariant"
)
;
assert
(_succ
!= Self
,
"invariant"
)
;
assert
(
(
(oop
)
(
object
(
)
)
)
-
>
mark
(
)
== markOopDesc
:
:
encode
(
this
)
,
"invariant"
)
;
// The thread -- now the owner -- is back in vm mode.
// Report the glorious news via TI,DTrace and jvmstat.
// The probe effect is non-trivial. All the reportage occurs
// while we hold the monitor, increasing the length of the critical
// section. Amdahl's parallel speedup law comes vividly into play.
//
// Another option might be to aggregate the events (thread local or
// per-monitor aggregation) and defer reporting until a more opportune
// time -- such as next time some thread encounters contention but has
// yet to acquire the lock. While spinning that thread could
// spinning we could increment JVMStat counters, etc.
DTRACE_MONITOR_PROBE
(contended__entered
,
this
,
object
(
)
, jt
)
;
if
(
JvmtiExport
:
:
should_post_monitor_contended_entered
(
)
)
{
JvmtiExport
:
:
post_monitor_contended_entered
(jt
,
this
)
;
}
//java学习交流:737251827 进入可领取学习资源及对十年开发经验大佬提问,免费解答!
if
(
event
.
should_commit
(
)
)
{
event
.
set_klass
(
(
(oop
)
this
-
>
object
(
)
)
-
>
klass
(
)
)
;
event
.
set_previousOwner
(
(
TYPE_JAVALANGTHREAD
)_previous_owner_tid
)
;
event
.
set_address
(
(
TYPE_ADDRESS
)
(uintptr_t
)
(
this
-
>
object_addr
(
)
)
)
;
event
.
commit
(
)
;
}
if
(
ObjectMonitor
:
:_sync_ContendedLockAttempts
!=
NULL
)
{
ObjectMonitor
:
:_sync_ContendedLockAttempts
-
>
inc
(
)
;
}
}
白话翻译
同学A在使用自习教室的时候,同学B在同一时刻也想使用自习教室,那就发生了竞争关系。所以同学B在A运行过程中,加入等待队列。如果此时同学C也要使用该教室,也会加入等待队列。等同学A使用结束,同学B和C将竞争自习教室。
自旋优化
自旋优化比较简单,如果将其他线程加入等待队列,那之后唤醒并运行线程需要消耗资源,所以设计人员让其空转一会,看看线程能不能一会结束了,这样就不要在加入等待队列。
白话来说,如果同学A在使用自习教室,同学B可以回宿舍,等A使用结束再来,但是B回宿舍再来的过程需要1个小时,而A只要10分钟就结束了。所以B可以先不回宿舍,而是在门口等个10分钟,以防止来回时间的浪费。
结语
唉呀妈呀,终于结束了,累死了。终于将synchronized写完了,如果有不正确的地方,还需要各位指正。如果觉得写得还行,麻烦帮我点赞,评论哈。