synchronized介绍 -Java

sychronized 是 Java 中用于控制多线程访问共享资源的关键字。它能够确保在某一时刻只有一个线程可以访问某个资源,从而避免多线程的并发问题,如数据不一致、竞争条件等。synchronized 的实现依赖于 Java 内部的监视器锁(monitor lock)。

1. synchronized 的作用

synchronized 主要作用是实现线程间的互斥访问,通过锁机制保证:

  1. 互斥性:同一时间只有一个线程能够访问 synchronized 修饰的代码块或方法。
  2. 可见性:线程对共享变量的修改对其他线程是可见的,因为在释放锁之前,线程会将修改的值刷新到主内存中。

synchronized 可以用于以下几种场景:

  • 修饰方法:对整个方法进行同步,锁对象是当前实例对象(this)。
  • 修饰静态方法:对静态方法进行同步,锁对象是该类的 Class 对象。
  • 修饰代码块:对代码块进行同步,可以指定锁对象。

2. 使用方法

(1) 修饰实例方法

synchronized 修饰实例方法时,锁是当前对象实例 (this)。

public class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

  • 只有一个线程能同时执行 increment()getCount() 方法,因为它们都使用同一把锁(this)。

(2) 修饰静态方法

synchronized 修饰静态方法时,锁是类的 Class 对象

public class Counter {
    private static int count = 0;

    public static synchronized void increment() {
        count++;
    }

    public static synchronized int getCount() {
        return count;
    }
}
  • 静态方法的锁是 Class 对象,所有线程共享该锁。即便有多个实例对象,也只能有一个线程访问静态方法。

(3) 修饰代码块

synchronized 修饰代码块时,可以指定锁对象,通常是实例对象(this)或自定义对象。

public class Counter {
    private int count = 0;
    private final Object lock = new Object();

    public void increment() {
        synchronized (lock) {
            count++;
        }
    }

    public int getCount() {
        synchronized (lock) {
            return count;
        }
    }
}
  • 通过代码块锁定范围更小的代码,避免对整个方法加锁,从而提高效率。

3. synchronized 的实现原理

synchronized 的底层实现依赖于 JVM 的 Monitor(监视器) 锁机制,它是由操作系统的互斥锁(Mutex)实现的。

(1) Monitor 锁

  • 每个 Java 对象都可以作为一个锁,因为每个对象都包含一个对象头(Object Header),其中存储了锁的信息。
  • 当线程进入同步块或同步方法时,Monitor 锁会被锁定。其他线程需要等待该锁释放后才能继续执行。

(2) 锁的状态

synchronized 使用了不同级别的锁优化性能:

  1. 无锁状态:没有线程竞争时,锁处于无锁状态。
  2. 偏向锁:当只有一个线程访问同步代码时,JVM 会将该线程标记为偏向锁持有者,避免锁竞争的开销。
  3. 轻量级锁:当多个线程尝试获取锁时,JVM 会使用 CAS(Compare-And-Swap)操作进行加锁,避免线程挂起。
  4. 重量级锁:当线程竞争激烈时,锁会升级为重量级锁,线程进入阻塞状态。

(3) 锁的获取与释放

  • 线程进入 synchronized 块时,尝试获取对象的 Monitor 锁。
  • 锁被持有后,其他线程无法进入被锁定的代码块。
  • 当线程退出同步块或方法时,锁会被释放,其他线程可以继续竞争锁。

(4) Java 对象头

每个 Java 对象的对象头包含两个重要部分:

  • Mark Word:存储锁状态(无锁、偏向锁、轻量级锁、重量级锁)以及其他元数据(如哈希值)。
  • Class Pointer:指向对象的类元数据。

4. synchronized 的性能优化

为了提升性能,Java 在 JDK 1.6 及以后引入了多种锁优化机制:

  1. 偏向锁:当锁的竞争很少时,锁会偏向第一个访问它的线程,避免无意义的同步操作。如果其他线程尝试获取锁,偏向锁会撤销。
  2. 轻量级锁:使用 CAS 操作避免线程的挂起和恢复。如果线程竞争锁失败,轻量级锁会膨胀为重量级锁。
  3. 锁粗化:如果一个线程频繁进入和退出同步块,JVM 会将多个同步块合并为一个较大的同步块,减少锁操作的开销。
  4. 锁消除:如果 JVM 通过逃逸分析发现某些同步块中的锁是多余的(不会被其他线程访问),它会消除这些锁。

5. 常见问题

(1) synchronizedReentrantLock 的区别

  • synchronized 是 Java 的内置锁,而 ReentrantLock 是 Java 并发包(java.util.concurrent.locks)中的显式锁。
  • ReentrantLock 提供了更多的功能,比如可中断的锁获取、超时机制和公平锁等。
  • synchronized 更简洁,适合简单的同步场景。

(2) 性能问题

  • 在高并发环境下,synchronized 可能导致性能问题,特别是线程争抢激烈时会引发线程阻塞。
  • JDK 1.6 之后,synchronized 的性能已经大大优化,常见的锁竞争场景性能接近 ReentrantLock

(3) 死锁问题

使用 synchronized 时,需要注意避免死锁。比如两个线程分别持有两把锁,并试图获取对方的锁时,会陷入死锁。

Java碎碎念 文章被收录于专栏

来一杯咖啡,聊聊Java的碎碎念呀

全部评论

相关推荐

评论
4
1
分享

创作者周榜

更多
牛客网
牛客企业服务