详解synchronized

Synchronized 是 Java 中的一个关键字,用于实现线程同步。它确保在同一时刻,只有一个线程可以执行被 synchronized 修饰的代码块或方法,从而避免多线程并发访问共享资源时出现数据不一致的情况。主要用于解决多线程环境下的并发问题。

Synchronized的两种用法

1.同步方法:synchronized 用于方法时,表示整个方法是同步的。当一个线程调用该方法时,会自动获取当前对象的锁(对于非静态方法),或者类的锁(对于静态方法),其他线程必须等待该线程释放锁后才能调用该方法。

public synchronized void syncMethod() {
    // 同步方法
}

public static synchronized void staticSyncMethod() {
    // 静态同步方法
}

**非静态同步方法:**锁的是当前实例对象 (this)。
**静态同步方法:**锁的是当前类的类对象 (Class<?>)。
2.同步代码块:synchronized 也可以用于指定代码块,只同步该代码块,而不是整个方法。这样可以更加精确地控制锁的范围。

public void syncBlock() {
    synchronized(this) {
        // 同步代码块
    }
}

public static void staticSyncBlock() {
    synchronized(SynchronizedExample.class) {
        // 静态同步代码块
    }
}

**同步代码块:**可以通过 synchronized(锁对象) 的方式实现,其中锁对象可以是任意一个对象。当锁对象被一个线程持有时,其他线程无法获得该对象的锁。

synchronized 的底层原理

Synchronized 的实现依赖于对象头中的 Mark Word(标记字段)。在 Java 虚拟机中,每个对象都由对象头和实例数据组成。对象头中包含了 Mark Word,Mark Word 用来存储对象的运行时数据,比如哈希码、锁状态等。
锁的状态:
无锁状态: 对象没有被任何线程持有。
偏向锁: 当一个线程第一次获取锁时,JVM 会将该锁对象标记为偏向锁,并将当前线程的 ID 记录在对象头中。如果该线程再次进入同步块,不需要再进行复杂的加锁操作。
轻量级锁: 当偏向锁的对象被其他线程请求时,会升级为轻量级锁,此时会通过 CAS 操作尝试获取锁。
重量级锁: 当多个线程频繁争夺同一个锁,导致轻量级锁不断失败时,锁会升级为重量级锁,JVM 会通过操作系统的互斥量(Mutex)来进行加锁操作,这种方式会导致线程阻塞,性能较低。

monitorentermonitorexit 指令

在 Java 中,synchronized 关键字用于实现线程同步。其背后依赖于 JVM 中的两个字节码指令——monitorentermonitorexit,来实现对同步块的加锁和解锁操作。
1.monitorenter
1.每当线程进入被 synchronized 修饰的代码块时,JVM 会在这个位置插入 monitorenter 指令。
2.该指令的作用是尝试获取对象的监视器(即锁)。如果锁已经被其他线程持有,那么当前线程会进入阻塞状态,直到获得锁。
3.如果锁是空闲的,那么线程会获得该锁并继续执行同步块内的代码。
4.每个对象都关联一个监视器(monitor),一个线程对对象的监视器加锁后,其他线程就不能再访问该对象的 synchronized 代码块,直到锁被释放。
2.monitorexit
1.当线程即将退出同步块时,JVM 会插入 monitorexit 指令。
2.该指令的作用是释放之前通过 monitorenter 获取的对象的监视器。
3.如果线程执行完同步块的所有代码或者由于异常提前退出同步块,那么 JVM 会确保在适当的位置插入 monitorexit 指令,以释放锁。
4.释放锁后,其他被阻塞的线程可以尝试重新获取该锁。

同步代码块的实现

假设有以下Java代码

public void syncMethod() {
    synchronized(this) {
        // synchronized block
    }
}

编译成字节码后,其对应的JVM字节码可能如下:

0: aload_0               // 加载 this 到栈顶
1: dup                   // 复制栈顶的 this(以便后续使用)
2: monitorenter          // 获取 this 对象的监视器(锁)
3: // synchronized block
4: aload_0               // 加载 this 到栈顶
5: monitorexit           // 释放 this 对象的监视器(锁)
6: goto 10               // 跳转到后续代码执行
7: aload_0               // 异常处理块
8: monitorexit           // 在异常处理块中释放监视器(锁)
9: athrow                // 抛出异常
10: // 后续代码

monitorenter 和 monitorexit 的工作流程

获取锁 (monitorenter):
1.当线程执行到 monitorenter 指令时,JVM 会尝试获取对象的监视器。
2.如果监视器的计数器为 0(即没有其他线程持有锁),当前线程会成功获取锁,监视器计数器加 1,并记录持有锁的线程 ID。
3.如果该线程已经持有锁,则监视器的计数器继续增加,表明锁是可重入的。
4.如果锁已被其他线程持有,则当前线程会进入阻塞状态,直到锁被释放。
释放锁 (monitorexit):
1.当线程执行到 monitorexit 指令时,JVM 会将监视器的计数器减 1。
2.如果计数器减到 0,说明锁已经完全释放,JVM 会将持有锁的线程 ID 清空,并唤醒等待该锁的其他线程。
3.如果计数器不为 0,表明锁还在持有状态,其他线程仍无法获取该锁。

异常处理与锁的释放

为了确保在异常情况下锁能够正确释放,JVM 在编译同步块时,会自动生成异常处理代码,在任何异常发生时调用 monitorexit 指令。这确保了即使线程在同步块中抛出异常,锁也会被释放,避免死锁的发生。

synchronized 的注意事项

性能: 相对于普通代码块或方法,synchronized 代码块或方法在某些情况下会带来性能损耗,尤其是在高并发场景下。可以考虑使用其他并发工具类(如 ReentrantLock)来替代 synchronized,获得更灵活的锁控制。
死锁问题: 如果多个线程在不同的锁上相互等待,可能会导致死锁问题。因此在使用 synchronized 时,要谨慎设计锁的顺序和持有时长。
锁的粒度: 尽量缩小锁的粒度,只锁定真正需要保护的代码块,避免影响其他代码的执行效率。

构造方法可以用 synchronized 修饰么?

构造方法不能被 synchronized 修饰。Java 不允许构造方法使用 synchronized 关键字,因为构造方法用于创建对象,而在对象未完全创建之前,不存在其他线程访问该对象的情况。此外,使用 synchronized 关键字没有意义,因为无法在构造过程中锁定该对象。

JDK 1.6 之后的 synchronized 底层优化

JDK 1.6 对 synchronized 进行了大量优化,主要包括:
偏向锁: 在没有锁竞争的情况下,线程会将锁标记为“偏向”状态,避免了不必要的加锁和解锁操作。
轻量级锁: 通过 CAS 操作避免了重量级锁的使用,降低了锁的竞争开销。
自旋锁: 在锁短时间内被持有时,线程会进行自旋等待,而不是直接阻塞,减少了上下文切换的开销。

synchronizedvolatile 的区别

synchronized:
用于解决线程同步问题,确保同一时间只有一个线程执行某段代码。
既可以用于方法也可以用于代码块。
synchronized 不仅保证了可见性,还保证了操作的原子性。
volatile
用于解决变量在多个线程间的可见性问题,确保变量的修改对其他线程立即可见。
只能用于修饰变量,不能用于方法或代码块。
volatile 只能保证可见性,不能保证操作的原子性。

回答: synchronizedJava中用于实现线程同步的关键字。它可以用于修饰方法或代码块,以确保在同一时间只有一个线程可以访问被修饰的代码。\[1\]在Java中,synchronized关键字具有可重入性,这意味着一个线程可以多次获得同一个锁。例如,子类SynchronizedUsageChild继承自父类SynchronizedUsage,并重写了同步方法synchronizedMethod1。在子类的同步方法中,可以调用父类的同步方法,这验证了可重入锁的特性。\[1\] 此外,synchronized还可以作用于一个类,用于实现对类的同步。在一个类的方法中使用synchronized(ClassName.class)来实现对该类的同步。\[2\]另外,synchronized还可以修饰静态方法,使得该方法在多线程环境下同步执行。\[3\] 总之,synchronized关键字在Java中是实现线程同步的重要工具,它可以用于修饰方法、代码块或类,以确保在多线程环境下的线程安全性。 #### 引用[.reference_title] - *1* *3* [synchronized用法详解](https://blog.csdn.net/ganmaotong/article/details/124501478)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [synchronized详解](https://blog.csdn.net/m0_53474063/article/details/112389756)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值