Skip to content

Commit 05751f7

Browse files
authored
Update java关键字---synchronized.md
1 parent 51442a7 commit 05751f7

File tree

1 file changed

+12
-12
lines changed

1 file changed

+12
-12
lines changed

04.彻底理解synchronized/java关键字---synchronized.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
在java代码中使用synchronized可是使用在代码块和方法中,根据Synchronized用的位置可以有这些使用场景:
3333

3434

35-
![Synchronized的使用场景](https://user-gold-cdn.xitu.io/2018/4/30/16315cc79aaac173?w=700&h=413&f=png&s=33838)
35+
![Synchronized的使用场景](https://github.com/fancycoderzf/Java-concurrency/blob/master/04.%E5%BD%BB%E5%BA%95%E7%90%86%E8%A7%A3synchronized/synchronized%E7%9A%84%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF.png)
3636

3737

3838
如图,synchronized可以用在**方法**上也可以使用在**代码块**中,其中方法是实例方法和静态方法分别锁的是该类的实例对象和该类的对象。而使用在代码块中也可以分为三种,具体的可以看上面的表格。这里的需要注意的是:**如果锁的是类对象的话,尽管new多个实例对象,但他们仍然是属于同一个类依然会被锁住,即线程之间保证同步关系**
@@ -55,7 +55,7 @@
5555

5656

5757

58-
![SynchronizedDemo.class](https://user-gold-cdn.xitu.io/2018/4/30/16315cce259af0d2?w=700&h=330&f=png&s=68919)
58+
![SynchronizedDemo.class](https://github.com/fancycoderzf/Java-concurrency/blob/master/04.%E5%BD%BB%E5%BA%95%E7%90%86%E8%A7%A3synchronized/synchronizedDemo.class.png)
5959

6060

6161
如图,上面用黄色高亮的部分就是需要注意的部分了,这也是添Synchronized关键字之后独有的。执行同步代码块后首先要先执行**monitorenter**指令,退出的时候**monitorexit**指令。通过分析之后可以看出,使用Synchronized进行同步,其关键就是必须要对对象的监视器monitor进行获取,当线程获取monitor后才能继续往下执行,否则就只能等待。而这个获取的过程是**互斥**的,即同一时刻只有一个线程能够获取到monitor。上面的demo中在执行完同步代码块之后紧接着再会去执行一个静态同步方法,而这个方法锁的对象依然就这个类对象,那么这个正在执行的线程还需要获取该锁吗?答案是不必的,从上图中就可以看出来,执行静态同步方法的时候就只有一条monitorexit指令,并没有monitorenter获取锁的指令。这就是**锁的重入性**,即在同一锁程中,线程不需要再次获取同一把锁。Synchronized先天具有重入性。**每个对象拥有一个计数器,当线程获取该对象锁后,计数器就会加一,释放锁后就会将计数器减一**。
@@ -64,7 +64,7 @@
6464

6565
下图表现了对象,对象监视器,同步队列以及执行线程状态之间的关系:
6666

67-
![对象,对象监视器,同步队列和线程状态的关系](https://user-gold-cdn.xitu.io/2018/4/30/16315cd5fa7cf91c?w=700&h=261&f=png&s=54962)
67+
![对象,对象监视器,同步队列和线程状态的关系](https://github.com/fancycoderzf/Java-concurrency/blob/master/04.%E5%BD%BB%E5%BA%95%E7%90%86%E8%A7%A3synchronized/%E5%AF%B9%E8%B1%A1%EF%BC%8C%E5%AF%B9%E8%B1%A1%E7%9B%91%E8%A7%86%E5%99%A8%EF%BC%8C%E5%90%8C%E6%AD%A5%E9%98%9F%E5%88%97%E5%92%8C%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81%E7%9A%84%E5%85%B3%E7%B3%BB.png)
6868

6969
该图可以看出,任意线程对Object的访问,首先要获得Object的监视器,如果获取失败,该线程就进入同步状态,线程状态变为BLOCKED,当Object的监视器占有者释放后,在同步队列中得线程就会有机会重新获取该监视器。
7070

@@ -88,7 +88,7 @@
8888

8989

9090

91-
![synchronized的happens-before关系](https://user-gold-cdn.xitu.io/2018/4/30/16315ce6ea84f240?w=650&h=629&f=png&s=61572)
91+
![synchronized的happens-before关系](https://github.com/fancycoderzf/Java-concurrency/blob/master/04.%E5%BD%BB%E5%BA%95%E7%90%86%E8%A7%A3synchronized/synchronized%E7%9A%84happens-before%E5%85%B3%E7%B3%BB.png)
9292

9393

9494
在图中每一个箭头连接的两个节点就代表之间的happens-before关系,黑色的是通过程序顺序规则推导出来,红色的为监视器锁规则推导而出:**线程A释放锁happens-before线程B加锁**,蓝色的则是通过程序顺序规则和监视器锁规则推测出来happens-befor关系,通过传递性规则进一步推导的happens-before关系。现在我们来重点关注2 happens-before 5,通过这个关系我们可以得出什么?
@@ -100,14 +100,14 @@
100100

101101
废话不多说依旧先上图。
102102

103-
![线程A写共享变量](https://user-gold-cdn.xitu.io/2018/4/30/16315cef21fd3ad8?w=557&h=440&f=png&s=10816)
103+
![线程A写共享变量](https://github.com/fancycoderzf/Java-concurrency/blob/master/04.%E5%BD%BB%E5%BA%95%E7%90%86%E8%A7%A3synchronized/%E7%BA%BF%E7%A8%8BA%E5%86%99%E5%85%B1%E4%BA%AB%E5%8F%98%E9%87%8F.png)
104104

105105

106106
从上图可以看出,线程A会首先先从主内存中读取共享变量a=0的值然后将该变量拷贝到自己的本地内存,进行加一操作后,再将该值刷新到主内存,整个过程即为线程A 加锁-->执行临界区代码-->释放锁相对应的内存语义。
107107

108108

109109

110-
![线程B读共享变量](https://user-gold-cdn.xitu.io/2018/4/30/16315cf41661491e?w=564&h=458&f=png&s=14468)
110+
![线程B读共享变量](https://github.com/fancycoderzf/Java-concurrency/blob/master/04.%E5%BD%BB%E5%BA%95%E7%90%86%E8%A7%A3synchronized/%E7%BA%BF%E7%A8%8BB%E8%AF%BB%E5%85%B1%E4%BA%AB%E5%8F%98%E9%87%8F.png)
111111

112112
线程B获取锁的时候同样会从主内存中共享变量a的值,这个时候就是最新的值1,然后将该值拷贝到线程B的工作内存中去,释放锁的时候同样会重写到主内存中。
113113

@@ -153,13 +153,13 @@ CAS的实现需要硬件指令集的支撑,在JDK1.5后虚拟机才可以使
153153

154154
在同步的时候是获取对象的monitor,即获取到对象的锁。那么对象的锁怎么理解?无非就是类似对对象的一个标志,那么这个标志就是存放在Java对象的对象头。Java对象头里的Mark Word里默认的存放的对象的Hashcode,分代年龄和锁标记位。32为JVM Mark Word默认存储结构为(注:java对象头以及下面的锁状态变化摘自《java并发编程的艺术》一书,该书我认为写的足够好,就没在自己组织语言班门弄斧了):
155155

156-
![Mark Word存储结构](https://user-gold-cdn.xitu.io/2018/4/30/16315cff10307a29?w=700&h=71&f=png&s=23717)
156+
![Mark Word存储结构](https://github.com/fancycoderzf/Java-concurrency/blob/master/04.%E5%BD%BB%E5%BA%95%E7%90%86%E8%A7%A3synchronized/Mark%20Word%E5%AD%98%E5%82%A8%E7%BB%93%E6%9E%84.png)
157157

158158
如图在Mark Word会默认存放hasdcode,年龄值以及锁标志位等信息。
159159

160160
Java SE 1.6中,锁一共有4种状态,级别从低到高依次是:**无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态**,这几个状态会随着竞争情况逐渐升级。**锁可以升级但不能降级**,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。对象的MarkWord变化为下图:
161161

162-
![Mark Word状态变化](https://user-gold-cdn.xitu.io/2018/4/30/16315d056598e4c2?w=700&h=151&f=png&s=47968)
162+
![Mark Word状态变化](https://github.com/fancycoderzf/Java-concurrency/blob/master/04.%E5%BD%BB%E5%BA%95%E7%90%86%E8%A7%A3synchronized/Mark%20Word%E7%8A%B6%E6%80%81%E5%8F%98%E5%8C%96.png)
163163

164164

165165

@@ -177,14 +177,14 @@ HotSpot的作者经过研究发现,大多数情况下,锁不仅不存在多
177177
偏向锁使用了一种**等到竞争出现才释放锁**的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。
178178

179179

180-
![偏向锁撤销流程](https://user-gold-cdn.xitu.io/2018/4/30/16315d0b13b37da4?w=567&h=736&f=png&s=72325)
180+
![偏向锁撤销流程](https://github.com/fancycoderzf/Java-concurrency/blob/master/04.%E5%BD%BB%E5%BA%95%E7%90%86%E8%A7%A3synchronized/%E5%81%8F%E5%90%91%E9%94%81%E6%92%A4%E9%94%80%E6%B5%81%E7%A8%8B.png)
181181

182182

183183
如图,偏向锁的撤销,需要等待**全局安全点**(在这个时间点上没有正在执行的字节码)。它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果线程不处于活动状态,则将对象头设置成无锁状态;如果线程仍然活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word**要么**重新偏向于其他线程,**要么**恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。
184184

185185
下图线程1展示了偏向锁获取的过程,线程2展示了偏向锁撤销的过程。
186186

187-
![偏向锁获取和撤销流程](https://user-gold-cdn.xitu.io/2018/4/30/16315cb9175365f5?w=630&h=703&f=png&s=160223)
187+
![偏向锁获取和撤销流程](https://github.com/fancycoderzf/Java-concurrency/blob/master/04.%E5%BD%BB%E5%BA%95%E7%90%86%E8%A7%A3synchronized/%E5%81%8F%E5%90%91%E9%94%81%E8%8E%B7%E5%8F%96%E5%92%8C%E6%92%A4%E9%94%80%E6%B5%81%E7%A8%8B.png)
188188

189189
> **如何关闭偏向锁**
190190
@@ -201,12 +201,12 @@ HotSpot的作者经过研究发现,大多数情况下,锁不仅不存在多
201201
轻量级解锁时,会使用原子的CAS操作将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。下图是两个线程同时争夺锁,导致锁膨胀的流程图。
202202

203203

204-
![轻量级锁加锁解锁以及锁膨胀](https://user-gold-cdn.xitu.io/2018/4/30/16315cb9193719c2?w=794&h=772&f=png&s=287958)
204+
![轻量级锁加锁解锁以及锁膨胀](https://github.com/fancycoderzf/Java-concurrency/blob/master/04.%E5%BD%BB%E5%BA%95%E7%90%86%E8%A7%A3synchronized/%E8%BD%BB%E9%87%8F%E7%BA%A7%E9%94%81%E5%8A%A0%E9%94%81%E8%A7%A3%E9%94%81%E4%BB%A5%E5%8F%8A%E9%94%81%E8%86%A8%E8%83%80.png)
205205

206206
因为自旋会消耗CPU,为了避免无用的自旋(比如获得锁的线程被阻塞住了),一旦锁升级成重量级锁,就不会再恢复到轻量级锁状态。当锁处于这个状态下,其他线程试图获取锁时,都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程就会进行新一轮的夺锁之争。
207207
## 3.5 各种锁的比较 ##
208208

209-
![各种锁的对比](https://user-gold-cdn.xitu.io/2018/4/30/16315cb91da523d9?w=800&h=193&f=png&s=116058)
209+
![各种锁的对比](https://github.com/fancycoderzf/Java-concurrency/blob/master/04.%E5%BD%BB%E5%BA%95%E7%90%86%E8%A7%A3synchronized/%E5%90%84%E7%A7%8D%E9%94%81%E7%9A%84%E5%AF%B9%E6%AF%94.png)
210210

211211
# 4. 一个例子 #
212212
经过上面的理解,我们现在应该知道了该怎样解决了。更正后的代码为:

0 commit comments

Comments
 (0)