JAVA中常见的锁------>可重入锁

本文介绍了可重入锁的概念,以Java的ReentrantLock和synchronized为例,展示了如何在代码中实现。通过一个Phone类的示例,演示了在多线程环境下,如何使用ReentrantLock进行递归调用,避免死锁。在示例中,当一个线程在执行过程中获取了锁,可以再次获取同一锁以进入内层同步方法。最后,讨论了解锁时必须正确配对,否则可能导致线程阻塞。

可重入锁

指的是通过线程外层函数获得锁之后,内层递归函数仍然能够获得该锁的代码,在一个线程外层方法获取锁的时候,在进入内层方法自动获取锁。

也就是说,线程可以进入任何一个已经拥有的锁所同步着的代码块

ReentranLock/Synchronized就是一个很典型的可重入锁,作用避免死锁

上代码

package com.example.demo;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Phone implements Runnable {
    public synchronized void SendSMS() throws Exception {
        System.out.println(Thread.currentThread().getId() + "\t invock SendSMS().....");
        SendEmail();
    }

    public synchronized void SendEmail() throws Exception {
        System.out.println(Thread.currentThread().getId() + "\t ###########invock SendEmail().....");
    }

    Lock lock = new ReentrantLock();


    @Override
    public void run() {
        get();

    }

    public void get() {
        try {
            lock.lock();
            lock.lock();
            //线程可以进入任何一个已经拥有的锁所同步着的代码块
            System.out.println(Thread.currentThread().getId() + "\t invock get().....");
            set();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
            lock.unlock();
        }
    }

    public void set() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getId() + "\t ##########invock set().....");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

public class ReenterLockDemo {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(() -> {
            try {
                phone.SendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "t1").start();


        new Thread(() -> {
            try {
                phone.SendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "t2").start();

         try {
               TimeUnit.SECONDS.sleep(1);
              } catch (Exception e) {
               e.printStackTrace();
           }

        System.out.println();
        System.out.println();
        System.out.println();
        System.out.println();
        System.out.println();

        Thread t3 = new Thread(phone);
        Thread t4 = new Thread(phone);
        t3.start();
        t4.start();

    }
}

运行结果:

12     invock SendSMS().....
12     ###########invock SendEmail().....
13     invock SendSMS().....
13     ###########invock SendEmail().....

14     invock get().....
14     ##########invock set().....
15     invock get().....
15     ##########invock set().....

总结:ReentranLock/Synchronized 是java开发中常见的可重入锁也叫做递归锁,通过代码验证,在调用get方法中,如果去掉get方法中其中的一个 lock.unlock(); 还会成功吗?答案显而易见不会成功,15 这把锁会被锁住卡在那里,并不会被输出....切记两两配对,加几次锁几次....

<think>我们正在讨论Redisson如何实现可重入的分布式。根据引用[1][2][3][4][5],Redisson使用Redis的hash结构存储信息,其中key是名称,field是客户端ID+线程ID,value是重入次数。获取时,先检查是否存在,若不存在则设置;若存在且是当前线程持有,则重入次数加1。释放时,重入次数减1,直到为0才删除。整个过程使用Lua脚本保证原子性。</think>### Redisson 可重入分布式实现原理 #### 一、核心数据结构 Redisson 使用 **Redis Hash 结构**存储信息: ```redis Key: "lock:business" // 名称 Field: "<客户端ID>:<线程ID>" // 持有者标识(如8743c9c0-0795-4907-87fd-6c719a6b4586:1) Value: <重入次数> // 的持有次数 ``` 示例数据: ```json { "8743c9c0-0795-4907-87fd-6c719a6b4586:1": 3 } ``` > 该结构表示客户端`8743...`的线程`1`已重入3次[^5] #### 二、加流程(原子操作) 通过 **Lua脚本** 实现原子操作: ```lua -- 参数说明:KEYS[1]=名称, ARGV[1]=超时时间, ARGV[2]=客户端ID:线程ID if (redis.call('exists', KEYS[1]) == 0) then -- 不存在:直接获取 redis.call('hincrby', KEYS[1], ARGV[2], 1) redis.call('pexpire', KEYS[1], ARGV[1]) return nil -- 返回nil表示成功 end if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then -- 当前线程已持有:重入次数+1 redis.call('hincrby', KEYS[1], ARGV[2], 1) redis.call('pexpire', KEYS[1], ARGV[1]) return nil end -- 被其他线程持有:返回剩余生存时间(毫秒) return redis.call('pttl', KEYS[1]) ``` > 脚本包含三个关键分支:首次获取、重入、冲突[^3] #### 三、释放流程 ```lua -- 参数说明:KEYS[1]=名称, KEYS[2]=解消息通道, ARGV[1]=超时时间, ARGV[2]=客户端ID:线程ID local counter = redis.call('hget', KEYS[1], ARGV[2]) if (counter == false) then return nil end if (tonumber(counter) > 1) then -- 重入次数>1:次数减1 redis.call('hincrby', KEYS[1], ARGV[2], -1) redis.call('pexpire', KEYS[1], ARGV[1]) return 0 else -- 最后一次持有:删除并发布解消息 redis.call('del', KEYS[1]) redis.call('publish', KEYS[2], ARGV[1]) return 1 end ``` #### 四、重入实现关键点 1. **线程标识唯一性** 组合`客户端ID(RedissonClient UUID) + 线程ID`,确保分布式环境下唯一[^3] 2. **重入计数机制** ```java void method1() { lock.lock(); try { method2(); // 嵌套调用 } finally { lock.unlock(); } } void method2() { lock.lock(); // 重入:计数+1 try { // ... } finally { lock.unlock(); // 计数-1 } } ``` 3. **续期机制** 后台线程(`Watchdog`)定期(默认10秒)检查: - 若线程仍持有,重置超时时间为30秒 - 防止业务未完成时自动过期[^4] #### 五、与其他实现对比 | 特性 | SETNX+EXPIRE | Redisson | |---------------------|--------------------------|--------------------------| | **可重入性** | ❌ 不支持 | ✔️ 基于Hash结构计数 | | **续期** | ❌ 需自行实现 | ✔️ Watchdog自动续期 | | **释放原子性** | ❌ DEL非原子 | ✔️ Lua脚本保证原子性 | | **等待队列** | ❌ 轮询消耗资源 | ✔️ Semaphore+Pub/Sub | #### 六、等待获取机制 当被占用时,通过 **Semaphore + Redis Pub/Sub** 实现高效等待: 1. 订阅释放消息通道 2. 使用`Semaphore.tryAcquire(ttl)`限时等待 3. 收到解通知后立即重试获取[^4] ```java // 伪代码实现 while (true) { Long ttl = tryAcquireLock(); // 尝试获取 if (ttl == null) break; // 获取成功 if (ttl > 0) { semaphore.tryAcquire(ttl, MILLISECONDS); // 限时等待 } else { semaphore.acquire(); // 阻塞等待 } } ``` > **设计优势**:Redisson 将复杂的分布式逻辑(可重入续期、等待队列)封装为原子操作,开发者只需关注业务代码[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值