3、简单锁的优化

总结:为了防止线程处理时间超过锁的自动释放时间后,出现误删除其他线程锁的情况,会在手动删除锁的时候判断该锁是否属于此线程,不属于则不删除。为了实现锁的重入,可以通过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。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容