什么是自旋锁
自旋锁(Spinlock)是一种用于多线程同步的机制,在尝试获取锁时,如果锁已经被其他线程持有,则当前线程不会立即被阻塞,而是会进入一个循环中反复尝试获取锁,直到成功为止。这种机制通过“自旋”等待锁释放,从而避免了线程的上下文切换,但在某些情况下也可能导致CPU资源的浪费。
工作原理
-
尝试获取锁:当一个线程需要访问共享资源时,它会尝试获取自旋锁。如果锁已经被其他线程持有,则当前线程不会立即进入阻塞状态。
-
自旋等待:当前线程会进入一个循环(通常称为“自旋”),在这个循环中它会不断检查锁是否已经被释放。这通常是通过一个原子操作(如CAS,Compare-And-Swap)来实现的,该操作会检查锁的状态并尝试将其设置为已锁定。
-
获取锁成功:如果锁已经被释放(即其他线程已经完成了对共享资源的访问并释放了锁),则当前线程会成功获取锁,并退出自旋循环。
-
执行临界区代码:一旦获取了锁,当前线程就可以安全地访问共享资源,并执行临界区代码。
-
释放锁:完成临界区代码的执行后,当前线程会释放锁,以便其他线程可以获取锁并访问共享资源。
优缺点
优点
-
避免上下文切换:由于自旋锁不会使线程进入阻塞状态,因此可以避免线程上下文切换的开销。这对于锁持有时间较短且上下文切换开销较大的场景来说是有益的。
-
减少线程调度开销:由于自旋锁不会使线程进入阻塞状态,因此线程调度器不需要为这些线程分配和回收资源,从而减少了线程调度的开销。
缺点
-
CPU资源浪费:如果锁持有时间较长,自旋锁会导致CPU资源的浪费。因为线程在不断自旋等待锁释放的过程中会占用CPU资源,而这些资源本可以用于其他有用的工作。
-
可能导致优先级反转:在优先级反转的场景中,低优先级的线程可能持有锁,而高优先级的线程在等待锁。如果高优先级的线程不断自旋等待锁释放,它可能会消耗大量的CPU资源,而低优先级的线程却得不到足够的CPU时间来释放锁。
适用场景
自旋锁适用于以下场景:
-
锁持有时间较短:如果锁的持有时间非常短(例如,几微秒到几十微秒),则自旋锁可以避免线程上下文切换的开销,并提高性能。
-
多核处理器环境:在多核处理器环境中,自旋锁可以更好地利用CPU资源。因为即使一个线程在等待锁释放时占用了CPU资源,其他核心仍然可以执行其他有用的工作。
-
避免线程切换开销较大的场景:在某些情况下,线程切换的开销可能非常大(例如,在实时系统中)。在这些情况下,使用自旋锁可以避免线程切换的开销,并提高系统的响应性。
自实现一个自旋锁
源码
package com.test;import java.util.concurrent.atomic.AtomicReference;/*** @ClassName : SpinLockTest* @Description: 自旋锁Case* @Author: liulianglin* @Date: 2024-10-27 9:30* @Version : 1.0*/
public class SpinLockTest {AtomicReference<Thread> reference = new AtomicReference<>(null);public void lock(){System.out.println(Thread.currentThread().getName() + "\t coming in and try lock....");while (!reference.compareAndSet(null, Thread.currentThread())){}System.out.println(Thread.currentThread().getName() + "\t get lock succ....");}public void unlock(){reference.compareAndSet(Thread.currentThread(), null);System.out.println(Thread.currentThread().getName() + "\t unlock and coming out....");}public static void main(String[] args) throws InterruptedException {SpinLockTest spinLockTest = new SpinLockTest();Thread t1 = new Thread(()->{spinLockTest.lock();try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}spinLockTest.unlock();}, "t1");t1.start();// 等待t1启动完毕Thread.sleep(1000);Thread t2 = new Thread(()->{spinLockTest.lock();try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}spinLockTest.unlock();}, "t2");t2.start();}}
运行效果
t1线程首先启动运行并率先占有锁,然后持有锁5秒钟。t2线程启动运行后,先尝试获取锁,一直获取不到则会不停的进行while循环,直到获取到锁为止。