java 中 自旋锁和互斥锁有什么区别?

124 阅读3分钟

在 java 编程中,自旋锁(Spin Lock) 和 互斥锁 (Mutex Lock) 都是用来实现线程同步的锁机制,它们有不同的工作原理和使用场景。

自旋锁(Spin Lock)

自旋锁是一种忙等待的锁机制。当一个线程试图获取自旋锁时,如果锁已经被其他线程持有,那么该线程不会立即进入阻塞状态,而是会不断地循环检查锁的状态,直到它能够获取锁为止。这种锁机制适用于锁持有时间非常短的场景,因为自旋锁避免了线程的上下文切换开销,但会消耗 CPU 资源。

示例代码:

import java.util.concurrent.atomic.AtomicBoolean;

public class SpinLock {
    private final AtomicBoolean lock = new AtomicBoolean(false);
    
    public void lock() {
        while (!lock.compareAndSet(false, true)) {
            // 自旋等待
        }
    }
    
    public void unlock() {
        lock.set(false);
    }
}

互斥锁(Mutex Lock)

互斥锁是一种阻塞锁机制。当一个线程试图获取互斥锁时,如果锁已经被其他线程持有,那么该线程将会进入阻塞状态,直到锁被释放。互斥锁通常由操作系统实现,能够有效地管理资源并减少CPU的浪费。Java中常用的互斥锁实现是ReentrantLock和synchronized关键字。

示例代码:

使用 synchronized 关键字:

synchronized 是 java 提供的原生关键字,用于在方法或代码块上加锁,以确保同一时间只有一个线程可以执行被同步的代码块。

public class MutexLockExample {
    private final Object lock = new Object();
    
    public void criticalSection() {
        synchronized (lock) {
            // 关键代码区
        }
    }
}

使用 ReentrantLock:

ReentrantLockjava.util.concurrent.locks 包下的一个类,提供了与 synchronized 类似的功能,但它更灵活,具有更强的功能,例如可以实现公平锁、可中断锁等。

import java.util.concurrent.locks.ReentrantLock;

public class MutexLockExample {
    private final ReentrantLock lock = new ReentrantLock();

    public void criticalSection() {
        lock.lock();
        try {
            // 关键代码区
        } finally {
            lock.unlock();
        }
    }
}

区别与应用场景

  • 自旋锁:

    • 优点:避免了线程的上下文切换开销,适用于锁持有时间极短的场景。
    • 缺点:如果锁持有时间较长,会导致CPU资源的浪费。
    • 应用场景:高性能计算、锁持有时间极短的并发场景。
  • 互斥锁:

    • 优点:由操作系统管理,适用于各种场景,能够有效地管理资源。
    • 缺点:线程在获取锁失败时会进入阻塞状态,存在上下文切换开销。
    • 应用场景:一般的并发编程场景,尤其是锁持有时间不确定或较长的场景。

在实际项目中一般使用哪种锁?

在实际项目编程中,一般情况下使用互斥锁 Mutex Lock 较多,主要是因为其使用简单,适用场景广泛,且安全可靠。

互斥锁的两种主要实现方式也就是上面说的这两种, synchronized 关键字 和 ReentrantLock 类。

对比一下这两种实现方式:

  • synchronized 关键字

    • 优点:语法简单,易于使用,自动处理锁的获取和释放;
    • 缺点:功能相对较少,例如无法中断线程,无法实现公平锁。
  • ReenTrantLock

    • 优点:功能强大,提供了更多的控制选项,例如可以实现公平锁、可中断锁、具有 tryLock() 方法 ,可以尝试获取锁而不阻塞;
    • 缺点:需要显式的获取和释放锁,代码相对复杂。

对于简单的同步需要求,尤其是在单一锁的情况下,使用 synchronized 关键字通常是最好的选择。

在需要更灵活的锁控制,或需要特殊功能(如公平锁、可中断锁)时,使用 ReentrantLock 会更合适。