(3)Synchronized关键字

41 阅读6分钟

synchronized 是 Java 中用于实现线程同步的关键字,它可以用于不同地方以确保线程之间的协调和互斥。以下是对 synchronized 关键字的详细解释和使用方式:

1. synchronized 的基本概念

synchronized 关键字用于保护临界区(也称为同步块),确保在同一时间只有一个线程可以访问被保护的代码块。这有助于解决多线程访问共享资源时可能出现的竞态条件和数据不一致性问题。

2. 使用方式

a. 同步方法

你可以将 synchronized 关键字应用于方法,将整个方法声明为同步方法。这将锁定整个方法,确保在同一时间只有一个线程可以执行它。

public synchronized void synchronizedMethod() {
    // 这个方法是同步的
    // 一次只能被一个线程执行
    // 其他线程必须等待
}

b. 同步块

你也可以使用同步块,将 synchronized 关键字应用于特定的代码块,而不是整个方法。这种方式更加灵活,只有被包裹的代码块会受到同步的保护。

public void someMethod() {
    // 非同步代码
    
    synchronized (lockObject) {
        // 这个代码块是同步的
        // 只有一个线程可以同时执行它
        // lockObject 是一个对象,用于指定同步锁
    }
    
    // 非同步代码
}

3. 同步锁对象

在使用 synchronized 关键字时,你需要指定一个同步锁对象。这个对象是线程锁的标识,可以是任何 Java 对象。多个线程可以使用相同的锁对象来实现互斥。

通常,你可以使用以下几种方式来指定同步锁对象:

  • 对象的实例:使用类的实例作为锁对象,确保多个线程之间同步。
public class SynchronizedExample {
    private Object lock = new Object();

    public void synchronizedMethod() {
        synchronized (lock) {
            // 同步代码块
        }
    }
}
  • this 关键字:在实例方法中,可以使用 this 关键字来表示当前实例作为锁对象。
public synchronized void synchronizedMethod() {
    // 同步代码块,锁定的是当前实例(this)
}
  • 类的 Class 对象:在静态方法中,可以使用类的 Class 对象作为锁对象。
public static synchronized void synchronizedStaticMethod() {
    // 同步代码块,锁定的是类的 Class 对象
}

4. 同步的作用范围

synchronized 关键字的作用范围可以是方法级别,也可以是代码块级别。通常,代码块级别的同步更加灵活,因为你可以选择性地同步需要保护的代码,而不必锁定整个方法。

5. 重入性

Java 中的 synchronized 关键字是可重入的,也就是说,如果一个线程已经获得了某个锁对象,它可以再次进入其他使用同一个锁对象的 synchronized 块而不被阻塞。这允许线程在嵌套同步块时不会陷入死锁。

6. 性能注意事项

尽管 synchronized 提供了强大的线程同步机制,但它也可能引入性能开销。在高并发的情况下,过多地使用 synchronized 可能导致程序性能下降。因此,在使用 synchronized 时需要慎重考虑,并考虑是否可以使用更高级的并发工具,如并发集合、java.util.concurrent 包中的工具,以及锁的粒度优化来减少竞争。

总之,synchronized 关键字是 Java 中实现线程同步的一种基本方式,它可以确保多线程访问共享资源时的互斥性,但需要谨慎使用以避免性能问题和死锁等并发问题。

7. 底层原理

synchronized 关键字的底层原理是基于对象监视器(Object Monitor)来实现线程同步。每个Java对象都有一个关联的对象监视器,也称为内部锁(Intrinsic Lock)或互斥锁(Mutex)。当你使用 synchronized 修饰一个方法或代码块时,实际上是在操作对象监视器,以下是 synchronized 底层原理的详细解释:

  1. 锁的获取:当一个线程进入一个使用 synchronized 关键字修饰的同步方法或同步代码块时,它会尝试获取该代码块关联的对象监视器(锁)。

  2. 锁的状态:对象监视器有两种状态:锁定状态(Locked)和非锁定状态(Unlocked)。刚开始,对象监视器是非锁定状态。

  3. 锁的持有:如果对象监视器当前处于非锁定状态,那么当前线程将成功获取锁,并将对象监视器状态改为锁定状态。此时,其他线程将无法获得相同锁,它们会被阻塞,等待锁的释放。

  4. 锁的释放:当线程退出同步方法或同步代码块时,它会释放持有的锁,将对象监视器状态改回非锁定状态。这使得等待的线程有机会获得锁并继续执行同步代码块。

  5. 等待队列:如果多个线程在同一时间竞争获取锁,其中一个线程会成功获取锁,而其他线程将进入等待队列。等待队列中的线程会在锁被释放时竞争获取锁。这种机制确保了线程之间的互斥和协调。

  6. 公平性:Java中的synchronized锁通常是非公平的。这意味着不是按照严格的顺序来分配锁,而是允许线程在某些情况下插队获得锁,以提高性能。不过,你也可以使用 ReentrantLock 类的公平锁来实现公平性。

  7. 重入性:Java的 synchronized 是可重入的,也就是说,如果一个线程已经获得了某个锁对象,它可以再次进入其他使用同一个锁对象的 synchronized 块而不被阻塞。这允许线程在嵌套同步块时不会陷入死锁。

  8. 底层实现:JDK的实现中通常会使用一些底层的操作系统原语来实现对象监视器和锁的管理,以确保线程的正确同步和互斥。

需要注意的是,JDK的不同版本可能会对synchronized的实现细节进行优化,包括引入偏向锁、轻量级锁和自旋锁等机制,以提高synchronized的性能。这些优化在减少锁争用的情况下可以减少线程的阻塞,提高并发性能。

总之,synchronized 关键字的底层原理是基于对象监视器和锁的管理来实现线程同步的。了解这些底层细节有助于更好地理解并发编程,并可以更好地设计和调优多线程应用程序。