Skip to content

Commit 4112dd5

Browse files
committed
change note
1 parent d2003d9 commit 4112dd5

15 files changed

Lines changed: 142 additions & 38 deletions

‎README.md‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,13 @@
5454
- [9.HashMap遍历时删除元素的有哪些实现方法?](docs/HashMap.md.md#HashMap遍历时删除元素的有哪些实现方法?)
5555
- [多线程](docs/JavaMultiThread.md)
5656
- [1.进程与线程的区别是什么?](docs/JavaMultiThread.md#进程与线程的区别是什么?)
57-
- [2.Java中单例有哪些写法?](docs/JavaMultiThread.md#Java中单例有哪些写法?)
58-
- [3.Java中创建线程有哪些方式?](docs/JavaMultiThread.md#Java中创建线程有哪些方式?)
59-
- [4.如何解决序列化时可以创建出单例对象的问题?](docs/JavaMultiThread.md#如何解决序列化时可以创建出单例对象的问题?)
57+
- [2.进程间如何通信?](docs/JavaMultiThread.md#进程间如何通信?)
58+
- [3.Java中单例有哪些写法?](docs/JavaMultiThread.md#Java中单例有哪些写法?)
59+
- [4.Java中创建线程有哪些方式?](docs/JavaMultiThread.md#Java中创建线程有哪些方式?)
60+
- [5.如何解决序列化时可以创建出单例对象的问题?](docs/JavaMultiThread.md#如何解决序列化时可以创建出单例对象的问题?)
61+
- [6.悲观锁和乐观锁是什么?](docs/JavaMultiThread.md#悲观锁和乐观锁是什么?)
62+
- [7.volatile 关键字有什么用?怎么理解可见性,一般什么场景去用可见性?](docs/JavaMultiThread.md#volatile 关键字有什么用?怎么理解可见性,一般什么场景去用可见性?)
63+
- [8.sychronize的实现原理是怎么样的?](docs/JavaMultiThread.md#sychronize的实现原理是怎么样的?)
6064
* Redis
6165
- [基础](docs/RedisBasic.md)
6266
- [1.Redis是什么?](docs/RedisBasic.md#Redis是什么?)

‎docs/JavaMultiThread.md‎

Lines changed: 135 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,13 @@
55

66

77
#### [1.进程与线程的区别是什么?](#进程与线程的区别是什么?)
8-
9-
#### [2.Java中单例有哪些写法?](#Java中单例有哪些写法?)
10-
11-
#### [3.Java中创建线程有哪些方式?](#Java中创建线程有哪些方式?)
12-
13-
#### [4.如何解决序列化时可以创建出单例对象的问题?](#如何解决序列化时可以创建出单例对象的问题?)
14-
#### [5.悲观锁和乐观锁是什么?](#悲观锁和乐观锁是什么?)
15-
16-
#### [6.volatile 关键字有什么用?怎么理解可见性,一般什么场景去用可见性?](#volatile 关键字有什么用)
8+
#### [2.进程间如何通信?](#进程间如何通信?)
9+
#### [3.Java中单例有哪些写法?](#Java中单例有哪些写法?)
10+
#### [4.Java中创建线程有哪些方式?](#Java中创建线程有哪些方式?)
11+
#### [5.如何解决序列化时可以创建出单例对象的问题?](#如何解决序列化时可以创建出单例对象的问题?)
12+
#### [6.悲观锁和乐观锁是什么?](#悲观锁和乐观锁是什么?)
13+
#### [7.volatile 关键字有什么用?怎么理解可见性,一般什么场景去用可见性?](#volatile 关键字有什么用?怎么理解可见性,一般什么场景去用可见性?)
14+
#### [8.sychronize的实现原理是怎么样的?](#sychronize的实现原理是怎么样的?)
1715

1816

1917

@@ -564,4 +562,131 @@ instance = new Singleton(); // instance是volatile变量
564562

565563
用于做状态位标志,如果多个线程去需要根据一个状态位来执行一些操作,使用volatile修饰可以保证内存可见性。
566564

567-
用于单例模式用于保证内存可见性,以及防止指令重排序。
565+
用于单例模式用于保证内存可见性,以及防止指令重排序。
566+
567+
### sychronize的实现原理是怎么样的?
568+
569+
```java
570+
public class SyncTest {
571+
public void syncBlock(){
572+
synchronized (this){
573+
System.out.println("hello block");
574+
}
575+
}
576+
public synchronized void syncMethod(){
577+
System.out.println("hello method");
578+
}
579+
}
580+
```
581+
582+
当SyncTest.java被编译成class文件的时候,`synchronized`关键字和`synchronized`方法的字节码略有不同,我们可以用`javap -v` 命令查看class文件对应的JVM字节码信息,部分信息如下:
583+
584+
```java
585+
{
586+
public void syncBlock();
587+
descriptor: ()V
588+
flags: ACC_PUBLIC
589+
Code:
590+
stack=2, locals=3, args_size=1
591+
0: aload_0
592+
1: dup
593+
2: astore_1
594+
3: monitorenter // monitorenter指令进入同步块
595+
4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
596+
7: ldc #3 // String hello block
597+
9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
598+
12: aload_1
599+
13: monitorexit // monitorexit指令退出同步块
600+
14: goto 22
601+
17: astore_2
602+
18: aload_1
603+
19: monitorexit // monitorexit指令退出同步块
604+
20: aload_2
605+
21: athrow
606+
22: return
607+
Exception table:
608+
from to target type
609+
4 14 17 any
610+
17 20 17 any
611+
612+
613+
public synchronized void syncMethod();
614+
descriptor: ()V
615+
flags: ACC_PUBLIC, ACC_SYNCHRONIZED //添加了ACC_SYNCHRONIZED标记
616+
Code:
617+
stack=2, locals=1, args_size=1
618+
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
619+
3: ldc #5 // String hello method
620+
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
621+
8: return
622+
623+
}
624+
```
625+
626+
对于`synchronized`关键字而言,`javac`在编译时,会生成对应的`monitorenter`和`monitorexit`指令分别对应`synchronized`同步块的进入和退出,有两个`monitorexit`指令的原因是为了保证抛异常的情况下也能释放锁,所以`javac`为同步代码块添加了一个隐式的try-finally,在finally中会调用`monitorexit`命令释放锁。
627+
628+
而对于`synchronized`方法而言,`javac`为其生成了一个`ACC_SYNCHRONIZED`关键字,在JVM进行方法调用时,发现调用的方法被`ACC_SYNCHRONIZED`修饰,则会先尝试获得锁。
629+
630+
#### 锁
631+
632+
这是网上看到的一个流程图:
633+
634+
![sychronize](../static/sychronize.png)
635+
636+
就是Java对象的内存布局其实由对象头+实例数据+对齐填充三部分组成,而对象头主要包含Mark Word+指向对象所属的类的指针组成。Mark Word主要用于存储对象自身的运行时数据,哈希码,GC分代年龄,锁标志等。
637+
638+
下面就是Mark Word的数据映射表
639+
640+
![image](../static/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f31312f32382f313637353964643162306239363236383f773d37323026683d32353026663d6a70656726733d3337323831.jpeg)
641+
642+
##### 偏向锁
643+
644+
根据上面的表来看,Mark Word后三位为101时,加锁对象的状态为偏向锁,偏向锁的意义在于同一个线程访问sychronize代码块时不需要进行加锁,解锁操作,性能开销更低(HotSpot[1]的作者经过研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。)
645+
646+
因为正常情况下,当一个线程访问同步块并获取轻量级锁时,需要进行CAS操作将对象头的锁记录里指向当前线程的栈中的锁记录,执行完毕后需要释放轻量级锁。如果是同一个线程多次访问sychronize代码块,多次获取和释放轻量级,开销会偏大,所以会一开始判断对象是无锁状态,会将对象头设置为偏向锁,并且这个的线程ID到Mark Word,后续同一线程判断加锁标志是偏向锁,并且线程ID一致就可以直接执行。
647+
648+
![img](../static/16315cb9175365f5.png)
649+
650+
#### 轻量级锁
651+
652+
JVM的开发者发现在很多情况下,在Java程序运行时,同步块中的代码都是不存在竞争的,不同的线程交替的执行同步块中的代码。这种情况下,用重量级锁是没必要的。因此JVM引入了轻量级锁的概念。
653+
654+
线程在执行同步块之前,JVM会先在当前的线程的栈帧中创建一个`Lock Record`,其包括一个用于存储对象头中的 `mark word`(官方称之为`Displaced Mark Word`)以及一个指向对象的指针。下图右边的部分就是一个`Lock Record`。
655+
656+
![img](../static/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f31312f32382f313637353964643162323461633733643f773d38363926683d33353126663d706e6726733d3331313531.png)
657+
658+
(1)轻量级锁加锁
659+
660+
线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并 将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用 CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁,自旋获取锁失败的次数达到一定次数后就会进行锁升级,将锁升级为重量级锁,当前线程就会被阻塞,直到获得轻量级锁的线程执行完毕,释放锁,唤醒阻塞的线程。
661+
662+
(2)轻量级锁解锁
663+
664+
轻量级解锁时,会使用原子的CAS操作将Displaced Mark Word替换回到对象头,如果成 功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。图2-2是 两个线程同时争夺锁,导致锁膨胀的流程图。
665+
666+
![img](../static/16315cb9193719c2.png)
667+
668+
### 重量级锁
669+
670+
重量级锁是我们常说的传统意义上的锁,其利用操作系统底层的同步机制去实现Java中的线程同步。
671+
672+
重量级锁的状态下,对象的`mark word`为指向一个堆中monitor对象的指针。
673+
674+
一个monitor对象包括这么几个关键字段:cxq(下图中的ContentionList),EntryList ,WaitSet,owner。
675+
676+
其中cxq ,EntryList ,WaitSet都是由ObjectWaiter的链表结构,owner指向持有锁的线程。
677+
678+
![image-20200516203630962](../static/image-20200516203630962.png)
679+
680+
当一个线程尝试获得锁时,如果该锁已经被占用,则会将该线程封装成一个ObjectWaiter对象插入到cxq的队列尾部,然后暂停当前线程。当持有锁的线程释放锁前,会将cxq中的所有元素移动到EntryList中去,并唤醒EntryList的队首线程。
681+
682+
如果一个线程在同步块中调用了`Object#wait`方法,会将该线程对应的ObjectWaiter从EntryList移除并加入到WaitSet中,然后释放锁。当wait的线程被notify之后,会将对应的ObjectWaiter从WaitSet移动到EntryList中。
683+
684+
#### 三种锁的优缺点对比
685+
686+
![image-20200516203659737](../static/image-20200516203659737.png)
687+
688+
参考文章:
689+
690+
[死磕Synchronized底层实现--概论](https://github.com/farmerjohngit/myblog/issues/12)
691+
692+
[浅谈偏向锁、轻量级锁、重量级锁](https://www.jianshu.com/p/36eedeb3f912)

‎docs/_sidebar.md‎

Lines changed: 0 additions & 25 deletions
This file was deleted.

‎static/16315cb9175365f5.png‎

156 KB
Loading

‎static/16315cb9193719c2.png‎

281 KB
Loading

‎static/640‎

-10.1 KB
Binary file not shown.

‎static/image-20200516203630962.png‎

219 KB
Loading

‎static/image-20200516203659737.png‎

175 KB
Loading

0 commit comments

Comments
 (0)