自己实现自旋锁与非自旋锁(从代码分析两者区别)

本文通过实例解析了自旋锁和非自旋锁在Java并发控制中的实现与测试,自旋锁通过循环重试获取锁,而非自旋锁则进入等待状态。自旋锁适用于低并发场景,非自旋锁适合高并发或长任务,讨论了它们的优缺点及适用场景。

1.自旋锁实现与测试:

package juc.lock;

import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

class SpinLock {
    //设置一个原子引用,引用的初始值时null,
    // 当引用值为null时代表没有线程获取锁
    // 当引用值为某个线程时,代表锁被该线程占有
    private final AtomicReference<Thread> reference = new AtomicReference<>();


    public void lock(){
        Thread thread = Thread.currentThread();
        System.out.println("线程:"+Thread.currentThread().getName()+"准备获取锁,时间:"+new Date().toString());
        int count = 1;
        //当引用的期望值时null(没有线程占用锁)时,才能结束循环(*****获取锁的自旋逻辑*****)
        while(!reference.compareAndSet(null,thread)){
            count++;
        }
        System.out.println("线程:"+Thread.currentThread().getName()+"获取到了锁,尝试获取次数:"+count+",时间:"+new Date().toString());
    }

    public void unlock(){
        Thread thread = Thread.currentThread();
        System.out.println("线程:"+Thread.currentThread().getName()+"准备释放锁,时间:"+new Date().toString());
        //志用当引用为当前线程时,才能释放锁
        if(reference.compareAndSet(thread,null)){
            System.out.println("线程:"+Thread.currentThread().getName()+"释放锁成功,时间:"+new Date().toString());
        }else{
            System.out.println("线程:"+Thread.currentThread().getName()+"释放锁失败,时间:"+new Date().toString());
        }
    }

}

public class SpinLockDemo {
    //检测
    public static void main(String[] args) {
        SpinLock lock = new SpinLock();
        Thread t1 = new Thread(()->{
                    //t1尝试获取锁
                    lock.lock();
                    try {  TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {  e.printStackTrace();  }
                    System.out.println("线程:"+Thread.currentThread().getName()+"做了一些事情,时间:"+new Date().toString());
                    lock.unlock();
                },"t1");
        Thread t2 = new Thread(()->{
            //t2未获取到锁,尝试先释放锁,看看会发生什么
            lock.unlock();
            //t2尝试获取锁
            lock.lock();
            try {  TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {  e.printStackTrace();  }
            System.out.println("线程:"+Thread.currentThread().getName()+"做了一些事情,时间:"+new Date().toString());
            lock.unlock();
        },"t2");
        t1.start();
        try {  TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) {  e.printStackTrace();  }
        t2.start();
    }

}

执行结果:

分析:

t1线程先执行,成功获取到锁,并开始执行自己的逻辑(耗时3S),同时在t1线程开始执行1s后,t2线程尝试获取锁,但是此时锁已经被t1占用,t2进入自旋,不断尝试获取锁,可以看到,一共尝试了260571185次,这个过程中t2线程是活动状态.

2.非自旋锁的实现与测试:

package juc.lock;

import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

class UsualLock {
    //设置一个原子引用,引用的初始值时null,
    // 当引用值为null时代表没有线程获取锁
    // 当引用值为某个线程时,代表锁被该线程占有
    private final AtomicReference<Thread> reference = new AtomicReference<>();


    public synchronized void lock() {
        Thread thread = Thread.currentThread();
        System.out.println("线程:"+Thread.currentThread().getName()+"准备获取锁,时间:"+new Date().toString());
        int count = 1;
        //当引用的期望值时null(没有线程占用锁)时,才能结束循环(*****获取锁的自旋逻辑*****)
        //当引用不是null时,线程休眠
        while(!reference.compareAndSet(null,thread)){
            count++;
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("线程:"+Thread.currentThread().getName()+"获取到了锁,尝试获取次数:"+count+",时间:"+new Date().toString());
    }

    public synchronized void unlock(){
        Thread thread = Thread.currentThread();
        System.out.println("线程:"+Thread.currentThread().getName()+"准备释放锁,时间:"+new Date().toString());
        //志用当引用为当前线程时,才能释放锁
        if(reference.compareAndSet(thread,null)){
            //释放锁成功,通知其他线程
            this.notifyAll();
            System.out.println("线程:"+Thread.currentThread().getName()+"释放锁成功,时间:"+new Date().toString());
        }else{
            System.out.println("线程:"+Thread.currentThread().getName()+"释放锁失败,时间:"+new Date().toString());
        }
    }

}

public class UsualLockDemo {
    //检测
    public static void main(String[] args) {
        UsualLock lock = new UsualLock();
        Thread t1 = new Thread(()->{
            //t1尝试获取锁
            lock.lock();
            try {  TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {  e.printStackTrace();  }
            System.out.println("线程:"+Thread.currentThread().getName()+"做了一些事情,时间:"+new Date().toString());
            lock.unlock();
        },"t1");
        Thread t2 = new Thread(()->{
            //t2未获取到锁,尝试先释放锁,看看会发生什么
            lock.unlock();
            //t2尝试获取锁
            lock.lock();
            try {  TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {  e.printStackTrace();  }
            System.out.println("线程:"+Thread.currentThread().getName()+"做了一些事情,时间:"+new Date().toString());
            lock.unlock();
        },"t2");
        t1.start();
        try {  TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) {  e.printStackTrace();  }
        t2.start();
    }

}

 执行结果:

 

分析:

t1线程先执行,成功获取到锁,并开始执行自己的逻辑(耗时3S),同时在t1线程开始执行1s后,t2线程尝试获取锁,但是此时锁已经被t1占用,t2线程调用了wait()方法,知道t1线程执行unlock()时调用了notifyAll()方法,t2线程才被唤醒,可以看到,t2线程一共尝试了2次获取锁,这个过程中t2线程是阻塞状态.

总结/优缺点:

        自旋锁与非自旋锁的核心区别在于,如果线程获取锁时,但这个锁已经被其他线程占用时,这个线程所执行的动作,执行循环重试的就是自旋锁,执行等待的就是非自旋锁.

        还有一个区别就是,自旋锁在释放锁的时候不需要通知其他线程(因为其他线程根本就没有进入等待阻塞状态),而非自旋锁释放锁时需要执行notify()/notifyAll()方法(其他线程进入了阻塞等待状态)

       自旋锁的方式时一种乐观的方式来获取锁,这种方式在并发量不是特别高的时候和任务执行时间比较短的时候比较好(不需要切换线程),但在超高并发或者任务执行时间长的时候需要慎用(每个线程在自旋的过程中都会消耗CPU资源,如果多个线程长时间处于自旋状态,那么对CPU资源的消耗可想而知.)

<think>好的,我现在需要回答用户关于FreeRTOS自旋锁与互斥锁比较的问题。首先,我需要确认自己对这两个概念的理解是否正确。自旋锁和互斥锁都是用于多任务环境中的同步机制,但它们的实现方式和适用场景不同。 自旋锁在获取锁的时候,如果锁已经被占用,任务会一直循环检查(自旋),直到锁可用。这种情况下,任务不会让出CPU,所以适用于等待时间短的场景,比如多核处理器上的临界区保护。但如果在单核系统上使用自旋锁,可能会导致优先级反转或者死锁的问题,因为任务一直占用CPU,其他任务无法运行释放锁。 而互斥锁在获取不到锁时,任务会进入阻塞状态,让出CPU给其他任务,直到锁被释放后被唤醒。这种方式适合等待时间较长的场景,可以有效避免CPU资源的浪费。另外,FreeRTOS的互斥锁有优先级继承机制,可以防止优先级反转的问题,这是自旋锁所不具备的。 接下来,我需要比较它们的几个关键点:工作机制、适用场景、CPU占用、优先级反转处理、实现方式。需要确保每个点都准确无误。例如,自旋锁的忙等待机制确实会持续占用CPU,而互斥锁的阻塞机制则不会。此外,互斥锁的优先级继承机制在FreeRTOS中是如何实现的,可能需要进一步确认文档是否正确。 另外,用户提到的系统级指令要求使用正确的LaTeX格式,所以在涉及数学表达式或代码的地方需要注意格式是否正确。比如,使用$...$包裹行内表达式,独立公式用$$...$$。不过在这个问题中可能涉及较少的数学符号,但如果有提到时间参数或公式,需要正确应用格式。 用户希望结构清晰,逐步解决问题,所以应该分点比较两者的不同,并给出适用场景和注意事项。需要避免技术性错误,比如混淆自旋锁和互斥锁的实现机制,或者错误地描述优先级继承的作用。 可能还需要查阅FreeRTOS的官方文档,确认自旋锁是否是FreeRTOS原生支持的部分,或者是否是用户通过其他机制实现的。因为标准的FreeRTOS主要提供的是互斥锁(mutex)和信号量(semaphore),而自旋锁通常在多核环境下使用,可能需要特定的移植层或实现。这一点需要明确说明,避免误导用户。 总结一下,回答的结构应该是先定义自旋锁和互斥锁,然后分点比较它们的机制、适用场景、优缺点,最后给出使用建议。需要确保信息准确,特别是关于FreeRTOS的具体实现细节,例如互斥锁的优先级继承是否默认启用,自旋锁在单核系统中的问题等。 可能的误区是认为FreeRTOS原生支持自旋锁,但实际上可能需要依赖特定的硬件或多核支持。因此,在回答时需要指出自旋锁在FreeRTOS中的使用情况,可能需要用户自行实现或依赖于多核环境。同时,强调互斥锁在FreeRTOS中的标准支持和其特性,如优先级继承。 最后,检查是否满足用户的所有要求,特别是LaTeX格式和结构清晰的中文回答。确保没有技术错误,并且内容易于理解,帮助用户正确选择适合的同步机制。</think>FreeRTOS中自旋锁(Spinlock)与互斥锁(Mutex)是两种不同的同步机制,适用于多任务环境中的资源共享保护。以下是它们的对比分析: --- ### **1. 工作机制** - **自旋锁** 采用**忙等待(Busy-Wait)**机制。若任务尝试获取锁时发现已被占用,则持续循环检查(自旋),直到锁释放。 示例伪代码: ```c while (锁不可用) { /* 自旋 */ } 进入临界区; ``` - **互斥锁** 采用**阻塞等待(Blocking)**机制。若锁不可用,任务主动让出CPU,进入阻塞状态,待锁释放后由调度器唤醒。 示例伪代码: ```c if (锁不可用) { 阻塞当前任务; } 进入临界区; ``` --- ### **2. 适用场景** - **自旋锁** - **多核系统**:适用于**短临界区**(操作耗时极短),避免上下文切换的开销。 - **中断上下文**:某些场景下中断服务例程(ISR)需共享资源。 - **单核系统的特殊需求**:需结合优先级策略谨慎使用。 - **互斥锁** - **单核/多核系统**:适用于**长临界区**(操作可能耗时较长),避免忙等待浪费CPU。 - **任务间同步**:需支持优先级继承(Priority Inheritance)以防止优先级反转。 --- ### **3. CPU占用与效率** | 特性 | 自旋锁 | 互斥锁 | |-----------------|---------------------------|---------------------------| | **CPU占用** | 忙等待期间持续占用CPU | 阻塞期间不占用CPU | | **切换开销** | 无上下文切换 | 需上下文切换 | | **适用场景** | 临界区极短(如原子操作) | 临界区较长(如文件操作) | --- ### **4. 优先级反转问题** - **自旋锁**: **不解决优先级反转**。若高优先级任务因自旋锁被低优先级任务占用而等待,可能导致系统死锁(单核尤其严重)。 - **互斥锁**: 支持**优先级继承**(FreeRTOS默认启用)。当低优先级任务持有锁时,临时提升其优先级至高优先级任务的等级,避免反转。 --- ### **5. FreeRTOS实现差异** - **自旋锁**: FreeRTOS未原生提供自旋锁,但可通过原子操作或特定硬件指令(如ARM的LDREX/STREX)实现。 **依赖多核环境**:单核系统中自旋锁可能导致任务饥饿(需配合中断禁用)。 - **互斥锁**: FreeRTOS原生支持`xSemaphoreCreateMutex()`,通过二进制信号量实现,默认启用优先级继承。 **单核友好**:通过任务阻塞/唤醒机制适配单核调度。 --- ### **6. 关键对比总结** | 对比项 | 自旋锁 | 互斥锁 | |----------------|------------------------------|------------------------------| | **等待方式** | 忙等待 | 阻塞等待 | | **CPU利用率** | 低(忙等待浪费资源) | 高(释放CPU给其他任务) | | **适用核心数** | 多核优先 | 单核/多核通用 | | **优先级反转** | 不处理 | 优先级继承解决 | | **实现复杂度** | 需手动实现(依赖硬件/多核) | 原生API支持 | --- ### **7. 使用建议** 1. **多核系统**: - 短临界区 → 自旋锁(减少上下文切换开销)。 - 长临界区 → 互斥锁(避免CPU浪费)。 2. **单核系统**: - **避免自旋锁**(忙等待可能导致死锁),优先使用互斥锁。 3. **中断上下文**: - 若需共享资源,使用自旋锁(互斥锁可能导致任务切换,中断中不可用)。 4. **实时性要求**: - 高实时性短操作 → 自旋锁; - 普通任务 → 互斥锁。 --- ### **8. 示例场景** - **自旋锁适用**:多核共享计数器的原子递增操作(耗时极短)。 - **互斥锁适用**:任务需长时间访问SD卡文件(期间可让出CPU)。 --- 通过合理选择自旋锁与互斥锁,可优化FreeRTOS系统的实时性和资源利用率。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值