首页 > 编程笔记 > Java笔记 阅读:20

Java自旋锁的3种实现方式(非常详细)

自旋锁(Spinlock)是一种用于多线程同步的锁,其基本思想是,当一个线程尝试获取已经被其他线程持有的锁时,该线程不会立即进入阻塞状态,而是在循环中不断检查锁是否已经释放,即线程“自旋”等待锁的释放。

自旋锁的特点是线程在等待获取锁时不会被挂起,而是进行忙等。这种方式的优势在于避免了线程的上下文切换开销,而上下文切换会带来相对较大的性能损失。如果锁只是被短暂持有,那么自旋锁会比传统的互斥锁更高效。

自旋锁的优点有以下两个方面:
当然,凡事有利就有弊,自旋锁的缺点有以下几个方面:
在开发中,Java 并没有提供自旋锁工具类,需要开发者自己设计和实现这些类。自旋锁的工作原理包括以下几个主要过程,实现时需要考虑:
在 Java 中,自旋锁可以通过多种方式实现,以下是一些常见的自旋锁实现示例。

Java原子操作类实现自旋锁

最简单的自旋锁可以使用 java.util.concurrent.atomic 包下的原子类(比如 AtomicBoolean 或 AtomicReference)实现。
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);
    }
}
在上述代码中,lock() 方法会在锁状态为可用(即 lock 属性值为 false)时退出循环,并把 lock 属性值设置为 true。如果锁已经被其他线程持有,当前线程会在循环中忙等。unlock() 方法会将 lock 属性值设置为 false,表示释放锁。

Java Unsafe类实现自旋锁

高级开发者可以使用 sun.misc.Unsafe 类中的低级别原子操作直接实现自旋锁,但是并不推荐使用 Unsafe 类,因为它是非标准的 API,且在将来的 Java 版本中可能被移除。
import sun.misc.Unsafe;
public class SpinLock {
    private volatile int lock = 0;
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long lockOffset;
    static {
        try {
            lockOffset = unsafe.objectFieldOffset(SpinLock.class.getDeclaredField("lock"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    public void lock() {
        while (!unsafe.compareAndSwapInt(this, lockOffset, 0, 1)) {
            // 自旋等待,直到锁被释放
        }
    }
    public void unlock() {
        lock = 0;
    }
}
在上述代码中,unsafe.compareAndSwapInt() 是一个原子操作,用来检查当前值是否等于预期值,如果是则更新它。

java.util.concurrent.locks.LockSupport实现自旋锁

使用 LockSupport 类的 park() 和 unpark() 方法也可以挂起和唤醒线程,使用 LockSupport 可以实现自旋锁,示例代码如下所示:
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
public class SpinLock {
    private AtomicReference<Thread> owner = new AtomicReference<>();
    public void lock() {
        Thread current = Thread.currentThread();
        while (!owner.compareAndSet(null, current)) {
            LockSupport.park(); // 当获取不到锁时挂起当前线程
        }
    }
    public void unlock() {
        Thread current = Thread.currentThread();
        if (owner.compareAndSet(current, null)) {
            LockSupport.unpark(current); // 其他线程释放锁并唤醒等待的线程
        }
    }
}
在上述代码中,在 lock() 方法中尝试循环获取锁,如果当前线程无法获取锁,就调用 park() 方法挂起当前线程,减少 CPU 消耗。当其他线程释放锁时,调用 unpark() 来唤醒等待的线程。这种实现方式可以看作一个自旋锁和阻塞锁的混合版本。

在 Java 中,java.util.concurrent 包提供了丰富的并发工具,比如 ReentrantLock,它可以作为替代 LockSupport 的方案,而且其功能通常优于我们自定义的锁的,它提供的更高级的功能包括公平性、条件变量和可中断的锁获取等。

自旋锁通常用在多 CPU 系统中,且仅适用于锁持有时间非常短的情况。对于自旋锁的任何实现,都必须非常小心地使用,以避免性能问题,例如,过度的自旋等待可能会导致 CPU 资源的浪费。

相关文章