美文网首页
3、简单锁的优化

3、简单锁的优化

作者: 欧阳的博客 | 来源:发表于2021-01-26 09:45 被阅读0次

    总结:为了防止线程处理时间超过锁的自动释放时间后,出现误删除其他线程锁的情况,会在手动删除锁的时候判断该锁是否属于此线程,不属于则不删除。为了实现锁的重入,可以通过redis的hash数据结构来构造锁,hash表的field作为线程唯一id,value设置为重入次数即可。

    要防止删除别人的锁其实也很简单,就是把redis中锁的值设置成当前线程的唯一标识,然后删除的时候校对下,如果对不上就不删除了。下面代码采用uuid+当前线程id的方式生成。

    package com.dazhiyouqiu.api;
    import java.util.UUID;
    
    public class test {
    
        public static void main(String[] args) {
    
    
            //监听线程
            Thread monitor = new Thread(new Runnable() {
                @Override
                public void run() {
    
                    int time = 0;
                    Redis ru = new Redis();
    
    
                    while (time < 100000)
                        try {
                            Thread.sleep(500);
                            time = time + 500;
                            System.out.println("Time:" + time + "---->" + ru.get("lock"));
                        } catch (Exception e) {
    
                        }
    
    
                }
            });
    
            monitor.start();
    
    
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    Redis ru = new Redis();
    
    
                    //加锁,默认没获得锁
                    Boolean notGetLock = true;
                    String uuid= UUID.randomUUID().toString()+"_"+Thread.currentThread().getId();
    
                    //一直等到获得锁,再往下执行
                    String getLockResult = null;
                    while (notGetLock) {
                        getLockResult = ru.set("lock",uuid, 5);
                        if (getLockResult != null)
                            notGetLock = false;
    
                    }
    
    
                    try {
                        Thread.sleep(6000);
                    } catch (Exception e) {
    
                    }
    
    
                    //解锁
                    if(ru.get("lock").equals(uuid))
                        ru.delete("lock");
                    else {
                        System.out.println("当前线程"+Thread.currentThread().getName()+"无法删除锁,uuid不对");
                    }
                }
            });
    
            thread1.start();
    
    
            //等待5s后再启动线程2
            try {
                Thread.sleep(5000);
            } catch (Exception e) {
    
            }
    
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    Redis ru = new Redis();
    
    
                    //加锁,默认没获得锁
                    Boolean notGetLock = true;
                    //生成UUID作为value存储在redis
                    String uuid= UUID.randomUUID().toString()+"_"+Thread.currentThread().getId();
    
                    //一直等到获得锁,再往下执行
                    String getLockResult = null;
                    while (notGetLock) {
                        getLockResult = ru.set("lock", uuid, 5);
                        if (getLockResult != null)
                            notGetLock = false;
    
                    }
    
    
                    try {
                        Thread.sleep(4000);
                    } catch (Exception e) {
    
                    }
    
                    //解锁
                    if(ru.get("lock").equals(uuid))
                        ru.delete("lock");
                    else {
                        System.out.println("当前线程"+Thread.currentThread().getName()+"无法删除锁,uuid不对");
                    }
                }
            });
    
            thread2.start();
        }
    
    }
    
    

    运行结果如下:

    Time:500---->ee9bf6b2-82b5-4891-9d41-3c3761ad05c8_14
    Time:1000---->ee9bf6b2-82b5-4891-9d41-3c3761ad05c8_14
    Time:1500---->ee9bf6b2-82b5-4891-9d41-3c3761ad05c8_14
    Time:2000---->ee9bf6b2-82b5-4891-9d41-3c3761ad05c8_14
    Time:2500---->ee9bf6b2-82b5-4891-9d41-3c3761ad05c8_14
    Time:3000---->ee9bf6b2-82b5-4891-9d41-3c3761ad05c8_14
    Time:3500---->ee9bf6b2-82b5-4891-9d41-3c3761ad05c8_14
    Time:4000---->ee9bf6b2-82b5-4891-9d41-3c3761ad05c8_14
    Time:4500---->ee9bf6b2-82b5-4891-9d41-3c3761ad05c8_14
    Time:5000---->af60b26e-ca2b-4be9-99de-3e1dc53abd18_17
    Time:5500---->af60b26e-ca2b-4be9-99de-3e1dc53abd18_17
    当前线程Thread-1无法删除锁,uuid不对
    Time:6000---->af60b26e-ca2b-4be9-99de-3e1dc53abd18_17
    Time:6500---->af60b26e-ca2b-4be9-99de-3e1dc53abd18_17
    Time:7000---->af60b26e-ca2b-4be9-99de-3e1dc53abd18_17
    Time:7500---->af60b26e-ca2b-4be9-99de-3e1dc53abd18_17
    Time:8000---->af60b26e-ca2b-4be9-99de-3e1dc53abd18_17
    Time:8500---->af60b26e-ca2b-4be9-99de-3e1dc53abd18_17
    Time:9000---->null
    Time:9500---->null
    Time:10000---->null
    Time:10500---->null
    Time:11000---->null
    Time:11500---->null
    

    这样看似能解决问题,但是我们在第一节说过,如果在java中将两个原子操作拼接在一起的话依旧会有并发问题。我们在后面将查询锁和删除的操作分开了,所以就不是原子操作了。为了解决这个问题我们采用lua语言来操作。采用lua语言来操作redis的好处有这些:

    减少网络开销:多个请求通过脚本一次发送,减少网络延迟
    原子操作:将脚本作为一个整体执行,中间不会插入其他命令,无需使用事务
    复用:客户端发送的脚本永久存在redis中,其他客户端可以复用脚本
    可嵌入性:可嵌入JAVA,C#等多种编程语言,支持不同操作系统跨平台交互

    所以代码可以改成如下:

    package com.dazhiyouqiu.api;
    
    
    import java.util.UUID;
    
    public class test {
    
    
        public static void main(String[] args) {
    
    
            //监听线程
            Thread monitor = new Thread(new Runnable() {
                @Override
                public void run() {
    
                    int time = 0;
                    Redis ru = new Redis();
    
    
                    while (time < 100000)
                        try {
                            Thread.sleep(500);
                            time = time + 500;
                            System.out.println("Time:" + time + "---->" + ru.get("lock"));
                        } catch (Exception e) {
    
                        }
    
    
                }
            });
    
            monitor.start();
    
    
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    Redis ru = new Redis();
    
    
                    //加锁,默认没获得锁
                    Boolean notGetLock = true;
                    String uuid= UUID.randomUUID().toString()+"_"+Thread.currentThread().getId();
    
                    //一直等到获得锁,再往下执行
                    String getLockResult = null;
                    while (notGetLock) {
                        getLockResult = ru.set("lock",uuid, 5);
                        if (getLockResult != null)
                            notGetLock = false;
    
                    }
    
    
                    try {
                        Thread.sleep(6000);
                    } catch (Exception e) {
    
                    }
    
    
                    //解锁
                    if(ru.deleteWithValue("lock",uuid)){
                        System.out.println("当前线程"+Thread.currentThread().getName()+"手动解锁成功");
                    }
                     else {
                        System.out.println("当前线程"+Thread.currentThread().getName()+"无法删除锁,uuid不对");
                    }
                }
            });
    
            thread1.start();
    
    
            //等待5s后再启动线程2
            try {
                Thread.sleep(5000);
            } catch (Exception e) {
    
            }
    
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    Redis ru = new Redis();
    
    
                    //加锁,默认没获得锁
                    Boolean notGetLock = true;
                    //生成UUID作为value存储在redis
                    String uuid= UUID.randomUUID().toString()+"_"+Thread.currentThread().getId();
    
                    //一直等到获得锁,再往下执行
                    String getLockResult = null;
                    while (notGetLock) {
                        getLockResult = ru.set("lock", uuid, 5);
                        if (getLockResult != null)
                            notGetLock = false;
    
                    }
    
    
                    try {
                        Thread.sleep(4000);
                    } catch (Exception e) {
    
                    }
    
                    //解锁
                    if(ru.deleteWithValue("lock",uuid)){
                        System.out.println("当前线程"+Thread.currentThread().getName()+"手动解锁成功");
                    }
                    else {
                        System.out.println("当前线程"+Thread.currentThread().getName()+"无法删除锁,uuid不对");
                    }
                }
            });
    
            thread2.start();
        }
    
    }
    

    其中ru.deleteWithValue()方法的代码如下:

     public boolean deleteWithValue(String key,String value) {
            String luaScript = "if redis.call('get',KEYS[1]) == ARGV[1] then " +
                    "return redis.call('del',KEYS[1]) else return 0 end";
            return jedis.eval(luaScript, Collections.singletonList(key), Collections.singletonList(value)).equals(1L);
        }
    

    测试的结果如下:

    Time:500---->d92dcae5-876c-4778-bef5-a2c33effaf15_14
    Time:1000---->d92dcae5-876c-4778-bef5-a2c33effaf15_14
    Time:1500---->d92dcae5-876c-4778-bef5-a2c33effaf15_14
    Time:2000---->d92dcae5-876c-4778-bef5-a2c33effaf15_14
    Time:2500---->d92dcae5-876c-4778-bef5-a2c33effaf15_14
    Time:3000---->d92dcae5-876c-4778-bef5-a2c33effaf15_14
    Time:3500---->d92dcae5-876c-4778-bef5-a2c33effaf15_14
    Time:4000---->d92dcae5-876c-4778-bef5-a2c33effaf15_14
    Time:4500---->d92dcae5-876c-4778-bef5-a2c33effaf15_14
    Time:5000---->3f9bacca-f3de-4e3e-b1d5-be2cdc03f76f_17
    Time:5500---->3f9bacca-f3de-4e3e-b1d5-be2cdc03f76f_17
    当前线程Thread-1无法删除锁,uuid不对
    Time:6000---->3f9bacca-f3de-4e3e-b1d5-be2cdc03f76f_17
    Time:6500---->3f9bacca-f3de-4e3e-b1d5-be2cdc03f76f_17
    Time:7000---->3f9bacca-f3de-4e3e-b1d5-be2cdc03f76f_17
    Time:7500---->3f9bacca-f3de-4e3e-b1d5-be2cdc03f76f_17
    Time:8000---->3f9bacca-f3de-4e3e-b1d5-be2cdc03f76f_17
    Time:8500---->3f9bacca-f3de-4e3e-b1d5-be2cdc03f76f_17
    当前线程Thread-3手动解锁成功
    Time:9000---->null
    Time:9500---->null
    Time:10000---->null
    

    不过最好避免同时有两个线程同时访问同一个锁的情况,所以会在某个线程占用锁的时候创建一个守护线程,当锁快要超时的时候,守护线程给锁的过期时间续上。当线程结束的时候守护线程也会跟着结束,所以不用担心守护线程会遗留造成问题。
    至于重入锁的实现可以在这基础上做修改,通过redis的hash数据结构来构造锁,hash表的field作为线程唯一id,value设置为重入次数即可。同一线程多次获取锁时将value加1,解锁时将value减1。当value为0时锁才会进行释放。感兴趣的同学可以自己试着实现该特性。

    至此我们通过redis实现了简单的单机redis的分布式锁,其核心就是通过set的原子操作和lua语言运行redis的原子特性来实现的。但是当面对redis集群时我们也束手无策,当某个redis服务器宕机时,没法查询其他机器,也没法确保当前的锁是否已经被获取。

    因此下面将介绍Redisson实现Redis分布式锁:RedLock。

    相关文章

      网友评论

          本文标题:3、简单锁的优化

          本文链接:https://www.haomeiwen.com/subject/txxwzktx.html