[分布式锁] [Redisson实现] --- 对lock方法的使用误解

前言

看了很多用redisson实现分布式锁的博客, 对他们使用的方式我个人认为有一点点自己的看法, 接下来本文将以例子来验证为什么会有误解, 和看看正确的方式应该怎么写?

本文源代码: 源代码下载

大多数认为的写法

看到很多人都是这样写

RLock lock = redisson.getLock(KEY);
lock.lock()
// do your own work
lock.unlock()

简单看完源代码后, 我看到该方法会去调用一个响应一个中断的lockInterruptibly,此时我就有点疑惑了, 响应中断就是表示线程如果发生中断就不会在等待队列中等待(当然redisson是采用SUB/PUB的方式),(本文不分析源码哈,对该锁的源码分析会放到专门博客里面分析, 主要是验证该如何使用)可以看下图:

图片.png

上图中lock等方法会最终调用public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException 该方法会抛出异常, 然而lock方法并没有把这个异常抛出给使用者, 而是采用捕获异常,并且重新设置中断状态.

这下就有点明白了, 是不是需要用户自己来判断当前线程的状态来判断当前线程是否获得锁了呢?已经猜到这一步了, 接下来就需要验证一下自己的猜想

例子1:验证上面的写法

我是用maven项目构建的一个小项目,因此加入如下依赖

      <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>2.7.0</version>
      </dependency>

加入以下例子.

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.config.Config;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class TestDistributedRedisLock {

    private static CountDownLatch finish = new CountDownLatch(2);
    private static final String KEY = "testlock";
    private static Config config;
    private static Redisson redisson;
    static {
        config = new Config();
        config.useSingleServer().setAddress("127.0.0.1:6379");
        redisson = (Redisson)Redisson.create(config);
    }

    public static void main(String[] args) {
        Thread thread_1 = new LockWithoutBoolean("thread-1");
        Thread thread_2 = new LockWithoutBoolean("thread-2");
        thread_1.start();
        try {
            TimeUnit.SECONDS.sleep(10); // 睡10秒钟 为了让thread_1充分运行
            thread_2.start();
            TimeUnit.SECONDS.sleep(10); // 让thread_2 等待锁
            thread_2.interrupt(); // 中断正在等待锁的thread_2 观察thread_2是否会不会拿到锁
            finish.await();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            redisson.shutdown();
        }
    }

    static class LockWithoutBoolean extends Thread {
        private String name;
        public LockWithoutBoolean(String name) {
            super(name);
        }
        public void run() {
            RLock lock = redisson.getLock(KEY);
            lock.lock(10, TimeUnit.MINUTES);
            System.out.println(Thread.currentThread().getName() + " gets lock. and interrupt: " + Thread.currentThread().isInterrupted());
            try {
                TimeUnit.MINUTES.sleep(1);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                try {
                    lock.unlock();
                } finally {
                    finish.countDown();
                }
            }
            System.out.println(Thread.currentThread().getName() + " ends.");
        }
    }
}

在该例子中我启动了两个线程分别为thread-1thread-2, 并且让线程thread-1获得锁后休息1分钟, 线程thread-2在等待锁的过程中用主线程中断线程thread-2以此达到测试的目的.

接下来就需要观察结果, 在线程thread-2中断的时候会不会获得锁, 如何观察呢? 因为我们知道如果一个线程尝试去释放一个属于别的线程的锁的时候, 会抛出一个运行时异常叫做异常, 另外我们也可以通过观察redis里面数据的变化情况来判断thread-2到底有没有获得锁.

运行结果:

thread-1 gets lock. and interrupt: false
thread-2 gets lock. and interrupt: true
Exception in thread "thread-2" java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 9f178836-f7e1-44fe-a89d-2db52f399c0d thread-id: 21
    at org.redisson.RedissonLock.unlock(RedissonLock.java:353)
    at com.example.TestDistributedRedisLock$LockWithoutBoolean.run(TestDistributedRedisLock.java:53)
thread-1 ends.

从程序的角度看线程thread-2有没有获得锁: 可以看到在thread-1还没有结束的时候,也就是在thread-1在获得锁但是还没有释放锁的时候, thread-2由于被别的线程中断停止了等待从lock.lock(10, TimeUnit.MINUTES)的阻塞状态中返回继续执行接下来的逻辑,并且由于尝试去释放一个属于线程thread-1的锁而抛出了一个运行时异常导致该线程thread-2结束了, 然而thread-2完成了一系列操作后,线程thread-1才释放了自己的锁. 所以thread-2并没有获得锁,却执行了需要同步的内容,还尝试去释放锁.

从redis的角度看线程thread-2有没有获得锁: 下图便是整个运行期间KEY中内容的变化,从始至终redis中的testlockkey只产生了9f178836-f7e1-44fe-a89d-2db52f399c0d:20这一个key,很明显这个key是属于线程thread-1的,因为thread-1先获得了锁.如果thread-2获得了线程, 在key9f178836-f7e1-44fe-a89d-2db52f399c0d:20消失后应该产生一个属于线程thread-2的key.

图片.png

总结: 总上面两种角度的分析来看, thread-2在被别的线程中断后并没有获得锁, 所以这种写法不严谨!

例子2: 严谨的写法

看了例子1, 现在已经验证了我们的想法是对的, 在线程发生中断的时候该线程会立马从阻塞状态中返回, 并且没有获得锁. 因此我们看看第二个例子看看如何写会比较严谨.

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.config.Config;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class TestDistributedRedisLockWithBool {
    private static CountDownLatch finish = new CountDownLatch(2);
    private static final String KEY = "testlock";
    private static Config config;
    private static Redisson redisson;
    static {
        config = new Config();
        config.useSingleServer().setAddress("127.0.0.1:6379");
        redisson = (Redisson)Redisson.create(config);
    }

    public static void main(String[] args) {
        Thread thread_1 = new LockWithBoolean("thread-1");
        Thread thread_2 = new LockWithBoolean("thread-2");
        thread_1.start();
        try {
            TimeUnit.SECONDS.sleep(10); // 睡10秒钟 为了让thread_1充分运行
            thread_2.start();
            TimeUnit.SECONDS.sleep(10); // 让thread_2 等待锁
            thread_2.interrupt(); // 中断正在等待锁的thread_2 观察thread_2是否会不会拿到锁
            finish.await();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            redisson.shutdown();
        }
    }

    static class LockWithBoolean extends Thread {
        private String name;

        public LockWithBoolean(String name) {
            super(name);
        }

        public void run() {
            RLock lock = redisson.getLock(KEY);
            lock.lock(10, TimeUnit.MINUTES);
            if (!Thread.currentThread().isInterrupted()) {
                System.out.println(Thread.currentThread().getName() + " gets lock.");
                try {
                    TimeUnit.MINUTES.sleep(1);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    lock.unlock();
                }
            } else {
                System.out.println(Thread.currentThread().getName() + " does not get lock.");
            }
            System.out.println(Thread.currentThread().getName() + " ends.");
        }
    }
}

结果如下: 符合预期, 没有报异常, 线程都是正常退出.

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

推荐阅读更多精彩内容

  • 摘要: 我们已经知道,synchronized 是Java的关键字,是Java的内置特性,在JVM层面实现了对临界...
    kingZXY2009阅读 1,828评论 0 20
  • Lock和synchronized synchronized都知道是用于同步代码块和方法的,线程一旦获得对象锁,其...
    耳_总阅读 428评论 0 1
  • 从Java 5之后,在java.util.concurrent.locks包下提供了另外一种方式来实现同步访问,那...
    薛晨阅读 702评论 1 5
  • 方助生阅读 140评论 0 0
  • 会有那样的岁月吗,天空的风干净的吹来,草地一望无垠,温暖的阳光一点点铺满世界,我们都不再困惑。
    想念地瓜阅读 156评论 0 0