Java并发 - synchronized关键字

2024-01-09 15:18:22

synchronized关键字在Java中的主要作用是确保多线程环境下对共享资源的安全访问,避免出现竞态条件和数据不一致的问题。

它的主要意义包括以下几个方面:

  • 保证原子性:synchronized关键字能够确保被它修饰的方法或代码块在同一时刻只能被一个线程执行。
  • 解决竞态条件:使用synchronized关键字可以防止竞态条件的发生,确保对共享资源的操作按照预期的顺序执行。
  • 实现线程间通信:synchronized关键字还可以用于实现线程间的通信,通过锁的机制,确保在一个线程修改共享资源的同时,其他线程能够正确地等待或被唤醒。这有助于协调多个线程的工作,防止数据错乱。
  • 避免死锁:synchronized关键字的使用可以帮助避免死锁,因为它确保了在执行同步代码块时只有一个线程能够持有锁。

1. synchronized关键字的使用

1.1 synchronized 方法

整个synchronizedMethod方法都被锁定。当一个线程进入该方法时,其他线程必须等待,直到当前线程执行完毕释放锁。

public synchronized void synchronizedMethod() {
    // 同步代码块
    // ...
}

1.2 synchronized代码块

下面例子中,synchronized关键字只作用于lockObject对象的同步代码块,而不是整个方法。只有获取了lockObject的锁的线程才能够执行同步代码块,其他线程仍然可以同时执行非同步代码块。这种方式更加灵活,只对需要同步的部分进行同步,提高了程序的并发性能。

public void someMethod() {
    // 非同步代码块

    synchronized (lockObject) {
        // 同步代码块
        // ...
    }

    // 非同步代码块
    // ...
}

1.3 synchronized静态方法

它锁定的是类对象,因此在整个类中只有一个线程能够执行该静态方法。

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

    // 非静态方法
    public synchronized void instanceSynchronizedMethod() {
        // 实例同步代码块
        // ...
    }
}

1.4 使用对象作为锁

synchronized关键字需要一个对象作为锁,可以是任意对象。通常使用this作为锁,或者创建一个专门的对象作为锁。

public void example() {
    // 非同步代码块

    synchronized (this) {
        // 同步代码块
        // ...
    }

    // 非同步代码块
    // ...
}

2. synchronized的优缺点

  • 优点
    • 简单易用: synchronized是Java语言提供的原生支持,使用起来非常简单,不需要额外的引入或学习成本。
    • 自动释放锁: 在使用synchronized关键字时,当同步代码块执行完成或同步方法执行完成时,锁会自动释放,防止出现死锁的情况。
    • 确保数据一致性: synchronized关键字能够确保多线程环境下对共享资源的安全访问,避免竞态条件和数据不一致的问题。
    • 可读性强: 代码中使用synchronized,可以清晰地表达哪些代码需要同步执行,提高代码的可读性。
  • 缺点
    • 性能开销: 使用synchronized会引入性能开销。每次进入同步代码块,都需要获取锁,而且在某些情况下,等待锁的线程可能会导致性能下降。
    • 粒度过大: 在某些情况下,使用synchronized关键字可能导致锁的粒度过大,影响并发性能。例如,整个方法被同步可能限制了其他线程的并发执行。
    • 不可中断阻塞: 使用synchronized时,线程在等待锁的时候是不可中断的,即使有interrupt信号,线程也无法通过抛出异常退出同步代码块。
    • 单一锁对象: 对于对象上的同步,使用的是对象自身作为锁。这可能导致在某些情况下,需要同步多个不同的代码块,却只能使用同一个锁对象,可能引起性能问题或死锁的风险。
    • 无法应对复杂的并发控制需求: 在一些复杂的并发场景下,synchronized关键字可能无法满足更细粒度的控制需求,需要使用更为灵活的工具,如ReentrantLock等。

3. synchronized的特性

  1. 原子性(Atomicity): synchronized确保被它修饰的代码块或方法以原子方式执行,即在同一时刻只能有一个线程执行这段代码。这可以防止多个线程同时修改共享资源,从而避免了竞态条件的发生。
public synchronized void atomicOperation() {
    // 操作共享资源的一系列步骤
}
  1. 可见性(Visibility): synchronized保证一个线程对共享变量的修改,对其他线程是可见的。当一个线程获取锁时,它会把本地内存中的共享变量刷新到主内存,当释放锁时,会将本地内存中的修改刷新到主内存,从而保证可见性。

  2. **有序性(Ordering): **synchronized保证同步块内的操作按照程序的顺序执行。在同步块中的操作在执行时不会被重排序,从而确保了线程执行的有序性。

  3. **独占性(Exclusiveness): **synchronized是一种独占锁,当一个线程获取了锁,其他线程必须等待。这样确保在同一时刻只有一个线程能够执行被synchronized修饰的代码块或方法,避免了多线程之间的竞争。

  4. **可重入性(Reentrancy): **同一个线程可以多次获得同一个锁,而不会造成死锁。Java中的锁是可重入的,一个线程可以重复获取已经持有的锁,而不会被阻塞。

public synchronized void methodA() {
    // ...
    methodB(); // 可以在methodA中调用另一个同步方法methodB
    // ...
}

public synchronized void methodB() {
    // ...
}
  1. **锁的释放(Release): **当线程执行synchronized代码块或方法结束时,会自动释放锁,使得其他线程有机会获取锁并执行。

  2. 对监视器的支持(Monitor Support): 在Java中,每个对象都关联着一个监视器(Monitor)synchronized使用对象的监视器来实现同步。每个对象都有且只有一个监视器,而synchronized的锁就是由这个监视器来提供的。

4. JVM锁优化

Java虚拟机(JVM)采用了多种锁优化策略来提高并发性能,其中一些主要的优化策略包括:

  • 偏向锁(Biased Locking): 偏向锁是一项针对synchronized关键字的优化策略,旨在在没有竞争的情况下降低锁操作的成本。当一个线程获取锁时,偏向锁会记录下该线程的ID,并在之后该线程再次请求锁时,无需进行CAS操作,直接进入临界区。
-XX:+UseBiasedLocking     // 启用偏向锁
-XX:-UseBiasedLocking    // 禁用偏向锁
  • **轻量级锁(Lightweight Locking): **轻量级锁是一种在发生竞争时降低锁操作开销的优化策略。当多个线程争夺同一个锁时,偏向锁会升级为轻量级锁,通过CAS操作来避免传统锁的互斥开销。

  • **自旋锁(Spin Lock): **自旋锁是轻量级锁的一部分,通过自旋等待锁的释放来避免线程进入阻塞状态,减少线程切换的开销。自旋锁在JVM中被广泛应用于synchronized的优化。

  • **适应性自旋锁(Adaptive Spinning): **适应性自旋锁是JVM在一些情况下根据程序的执行情况动态调整自旋的次数。这项优化考虑到了不同的应用场景和硬件环境,根据实际情况来优化自旋锁的性能。

  • **锁消除(Lock Elimination): **锁消除是一种通过编译器对锁进行优化的策略。当编译器能够确定一段代码不可能存在竞争的时候,就会消除对这段代码的锁操作,从而提高程序的执行效率。

// 锁消除示例
public void lockEliminationExample() {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < 1000; i++) {
        sb.append("someText");  // 在这里锁可以被消除,因为StringBuilder是局部变量,不可能被其他线程访问
    }
}
  • **锁粗化(Lock Coarsening)😗*锁粗化是一种JVM的锁优化策略,它的主要目的是减少由于锁操作而引起的性能开销。锁粗化通过合并多个相邻的同步块为一个更大的同步块,减少线程在同步块之间的竞争。

5. java锁升级

在这里插入图片描述

Mark Word
存储内容标志位状态
对象哈希码、对象分代年龄01未锁定
指向锁记录的指针00轻量级锁定
指向重量级锁的指针10膨胀(重量级锁)
空、不需要记录信息11GC标记
偏向线程ID、偏向时间戳、对象分代年龄01可偏向

synchronized中锁的升级与对象头中的锁标志位相关,详情见java对象头部组成。

以下是synchronized 锁的升级过程的简要描述:

  • 偏向锁(Biased Locking)

    • 偏向锁的特点: 当一个线程获取锁时,偏向锁会记录获取锁的线程的 ID,并在对象头的 Mark Word 中保存这个线程 ID。在后续获取锁的操作中,如果是同一个线程再次获取锁,无需进行任何同步操作,可以直接获得锁。

    • 升级触发条件: 当有其他线程尝试获取锁时,偏向锁会失效,升级为轻量级锁。

  • 轻量级锁(Lightweight Locking)

    • 轻量级锁的特点: 当一个线程尝试获取锁时,JVM 会使用 CAS 操作尝试把对象头的 Mark Word 替换为指向线程栈中锁记录的指针,如果 CAS 操作成功,线程就获得了锁。

    • 升级触发条件: 当有多个线程竞争同一个锁时,轻量级锁会失效,升级为重量级锁。

  • 重量级锁(Heavyweight Locking)

    • 重量级锁的特点: 当轻量级锁失效时,JVM 会将锁升级为重量级锁,此时锁的实现涉及到底层的互斥量,会引起阻塞和唤醒操作。

文章来源:https://blog.csdn.net/qq_43678225/article/details/135478386
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。