sychronize的实现原理是怎么样的?
2 个回答
自己喜欢的东西又何必问别人好不好小王子-B612`synchronized` 是 Java 中用来实现线程同步的一种关键字,能够保证多个线程在并发访问共享资源时的**互斥性**。其原理是通过加锁和释放锁来控制同一时刻只有一个线程可以访问被保护的资源。 ### 1. **基本概念** `synchronized` 可以用来修饰方法或代码块,它的主要作用是**确保多个线程在访问共享资源时,不会同时进入被同步的方法或代码块**。当一个线程进入 `synchronized` 代码块或方法时,它会**获得一个锁**,其他线程想要访问这个同步块时,必须等待这个锁被释放。 ### 2. **synchronized 的实现原理** `synchronized` 的底层实现依赖于 **Java 对象的监视器锁(Monitor)**。每个 Java 对象都有一个关联的监视器锁,当一个线程进入同步块或方法时,它必须获得这个对象的监视器锁。如果其他线程已经持有了这个锁,那么当前线程就会被阻塞,直到持有锁的线程释放锁。 具体的原理可以分为以下几个步骤: #### 1. **获取锁** - 当一个线程进入 `synchronized` 修饰的代码块时,首先它会尝试获取对象的监视器锁。 - 如果锁没有被其他线程持有,那么线程可以成功获取锁,并进入同步代码块。 - 如果锁已经被其他线程持有,当前线程会进入**阻塞状态**,等待锁的释放。 #### 2. **执行代码** - 当线程成功获得锁后,它就可以安全地执行同步代码块中的代码。 - 在执行过程中,其他试图进入相同同步块的线程将无法继续执行,必须等待锁的释放。 #### 3. **释放锁** - 当线程执行完同步代码块或者抛出异常离开时,锁会被自动释放。 - 一旦锁被释放,其他等待的线程会被唤醒,继续尝试获取锁。获取到锁的线程可以进入同步代码块继续执行。 这个过程依赖于 **Java 虚拟机(JVM)** 的实现,JVM 通过**对象头(Object Header)** 中的某些标志位来记录对象的锁状态。 ### 3. **底层实现:对象头和 Monitor** Java 对象的内存布局中,**对象头**中包含了与同步相关的信息。 - **Mark Word**:Java 对象头中的一部分,用于存储对象的锁信息(包括是否被锁定,锁的状态,持有锁的线程等)。 当 `synchronized` 作用于一个对象时,**Mark Word** 中的锁状态就会发生变化。对象锁有四种状态:**无锁**、**偏向锁**、**轻量级锁** 和 **重量级锁**,这些状态会随着锁的竞争情况而变化。 #### 1. **无锁状态**: - 如果对象没有任何线程竞争,线程可以直接执行同步代码,不需要真正的加锁。 #### 2. **偏向锁**: - 偏向锁是为了优化只有一个线程访问同步块的场景。偏向锁允许线程多次进入同步块时不需要重新加锁,而是通过对象头中记录的线程 ID 来判断。如果偏向锁中的线程 ID 与当前线程匹配,那么线程可以直接进入同步块,无需加锁操作。 #### 3. **轻量级锁**: - 当另一个线程竞争同一个锁时,偏向锁会升级为轻量级锁。轻量级锁使用**自旋锁**,也就是线程会不断尝试获取锁,而不会立即进入阻塞状态。 #### 4. **重量级锁**: - 如果锁竞争严重,自旋超过一定次数后,轻量级锁会升级为**重量级锁**。此时,持有锁的线程执行完同步代码块后,其他线程会被挂起,并进入等待状态,直到持有锁的线程释放锁。 **Monitor** 是 Java 中用来实现对象锁的底层机制。它是一个同步工具,每个对象都有一个监视器锁,用于控制线程的进入和退出。当线程进入 `synchronized` 代码块时,必须获取对象的监视器锁。 ### 4. **synchronized 的作用范围** `synchronized` 关键字可以用在两种地方: 1. **同步方法**:使用 `synchronized` 修饰方法,表示整个方法都被同步保护。对象的实例方法使用 `this` 作为锁,静态方法使用类的 `Class` 对象作为锁。 - **实例方法**:`synchronized` 修饰的实例方法会使用该对象的实例锁。 - **静态方法**:`synchronized` 修饰的静态方法会使用该类的类锁(`Class` 对象锁)。 例子: ```java public synchronized void syncMethod() { // 同步方法 } public static synchronized void staticSyncMethod() { // 同步静态方法 } ``` 2. **同步代码块**:你可以只对某一块代码进行同步保护,`synchronized` 关键字后跟一个锁对象,这个锁可以是任何对象(常见的锁对象是 `this` 或者某个类的实例)。 例子: ```java public void syncBlock() { synchronized(this) { // 同步代码块 } } ``` ### 5. **synchronized 的锁优化** 在 JDK 1.6 后,`synchronized` 被大幅优化,使得它在性能上大大提升,主要包括以下几种优化: 1. **偏向锁**:适用于同一个线程多次进入同步块,避免不必要的加锁开销。 2. **轻量级锁**:当发生轻微的锁竞争时,通过自旋锁避免线程阻塞。 3. **重量级锁**:当竞争激烈时,线程才会进入阻塞状态,减少 CPU 资源的浪费。 ### 6. **总结** - **synchronized** 通过对象的 **Monitor**(监视器锁)来实现同步机制,每个对象都有一个关联的锁。 - **线程同步的原理**是:当一个线程持有了锁,其他线程只能等待锁的释放,才能访问被 `synchronized` 保护的代码。 - **锁的升级**(无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁)机制使得 `synchronized` 可以在不同并发程度下优化性能。 `synchronized` 的设计虽然简单,但其内部有非常复杂的机制来保证在并发访问共享资源时,线程不会相互干扰。编辑于 2022-12-12 19:44 •IP属地 美国举报评论收藏
世界上有两个我,一个想去远方,一个身不由己。菜菜```java public class SyncTest { public void syncBlock(){ synchronized (this){ System.out.println("hello block"); } } public synchronized void syncMethod(){ System.out.println("hello method"); } } ``` 当SyncTest.java被编译成class文件的时候,synchronized关键字和synchronized方法的字节码略有不同,我们可以用javap -v 命令查看class文件对应的JVM字节码信息,部分信息如下: ```java { public void syncBlock(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=1 0: aload_0 1: dup 2: astore_1 3: monitorenter // monitorenter指令进入同步块 4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 7: ldc #3 // String hello block 9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 12: aload_1 13: monitorexit // monitorexit指令退出同步块 14: goto 22 17: astore_2 18: aload_1 19: monitorexit // monitorexit指令退出同步块 20: aload_2 21: athrow 22: return Exception table: from to target type 4 14 17 any 17 20 17 any public synchronized void syncMethod(); descriptor: ()V flags: ACC_PUBLIC, ACC_SYNCHRONIZED //添加了ACC_SYNCHRONIZED标记 Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #5 // String hello method 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return } ``` 对于synchronized关键字而言,javac在编译时,会生成对应的monitorenter和monitorexit指令分别对应synchronized同步块的进入和退出,有两个monitorexit指令的原因是为了保证抛异常的情况下也能释放锁,所以javac为同步代码块添加了一个隐式的try-finally,在finally中会调用monitorexit命令释放锁。 而对于synchronized方法而言,javac为其生成了一个ACC_SYNCHRONIZED关键字,在JVM进行方法调用时,发现调用的方法被ACC_SYNCHRONIZED修饰,则会先尝试获得锁。 synchronized锁执行流程图 这是网上看到的一个流程图:  就是Java对象的内存布局其实由对象头+实例数据+对齐填充三部分组成,而对象头主要包含Mark Word+指向对象所属的类的指针组成。Mark Word主要用于存储对象自身的运行时数据,哈希码,GC分代年龄,锁标志等。  下面就是Mark Word的数据映射表 发布于 2022-02-04 22:36 •IP属地 黑龙江举报评论收藏
公众号进入网站体验更佳

