Redis实现分布式锁

1、无锁的例子



创建Maven工程。pom.xml文件如下:

 <dependencies>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.3.0</version>
        </dependency>
</dependencies>

创建一个Java类:

package lock.redisLock;

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

public class DisLockTest {
    private volatile int ticketNum = 10;// 剩余票数

    public static void main(String[] args) {
        new DisLockTest().test();
    }
    
    private void test() {
        SaleTicket saleTicket = new SaleTicket();
        new Thread(saleTicket,"窗口1").start();
        new Thread(saleTicket,"窗口2").start();
        new Thread(saleTicket,"窗口3").start();
        new Thread(saleTicket,"窗口4").start();
        new Thread(saleTicket,"窗口5").start();
    }

    class SaleTicket implements Runnable {
        @Override
        public void run() {
            while (ticketNum > 0) {
                try {
                    if (ticketNum > 0) {
                        TimeUnit.MILLISECONDS.sleep(ThreadLocalRandom.current().nextInt(1000, 2000)); // 随机时长,模拟业务逻辑时间
                        ticketNum--;
                        System.out.println(Thread.currentThread().getName() + ":  已售出1张票, 还剩余[" + ticketNum + "]张票!");
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

运行结果:

售票窗口4:  已售出1张票, 还剩余[9]张票!
售票窗口3:  已售出1张票, 还剩余[8]张票!
售票窗口2:  已售出1张票, 还剩余[7]张票!
售票窗口1:  已售出1张票, 还剩余[6]张票!
售票窗口5:  已售出1张票, 还剩余[5]张票!
售票窗口4:  已售出1张票, 还剩余[4]张票!
售票窗口3:  已售出1张票, 还剩余[3]张票!
售票窗口5:  已售出1张票, 还剩余[2]张票!
售票窗口2:  已售出1张票, 还剩余[1]张票!
售票窗口1:  已售出1张票, 还剩余[0]张票!
售票窗口4:  已售出1张票, 还剩余[-1]张票!
售票窗口2:  已售出1张票, 还剩余[-2]张票!
售票窗口5:  已售出1张票, 还剩余[-3]张票!
售票窗口3:  已售出1张票, 还剩余[-4]张票!

可以看到票出现了超售问题。

在多线程并发时,由于无锁,导致ticketCount读、写动作整体不是原子的。

2、本地锁、Atomic:

为解决上述超卖问题,可以使用本地锁、atomic来实现:

1)本地锁:

package lock.redisLock;

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

public class DisLockTest {
    private volatile int ticketNum = 10;// 剩余票数

    public static void main(String[] args) {
        new DisLockTest().test2();
    }
    
    private void test2() {
        SaleTicket2 saleTicket = new SaleTicket2();
        new Thread(saleTicket,"窗口1").start();
        new Thread(saleTicket,"窗口2").start();
        new Thread(saleTicket,"窗口3").start();
        new Thread(saleTicket,"窗口4").start();
        new Thread(saleTicket,"窗口5").start();
    }

    Lock lock = new ReentrantLock();
    class SaleTicket2 implements Runnable {
        @Override
        public void run() {
            while (ticketNum > 0) {
                lock.lock();
                try {
                    if (ticketNum > 0) {
                        TimeUnit.MILLISECONDS.sleep(ThreadLocalRandom.current().nextInt(1000, 2000)); // 随机时长,模拟业务逻辑时间
                        ticketNum--;
                        System.out.println(Thread.currentThread().getName() + ":  已售出1张票, 还剩余[" + ticketNum + "]张票!");
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }
}

2)atomic:

package lock.redisLock;

import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class DisLockTest2 {
    private volatile AtomicInteger ticketNum = new AtomicInteger(10);// 剩余票数

    public static void main(String[] args) {
        new DisLockTest2().test();
    }
    
    private void test() {
        SaleTicket saleTicket = new SaleTicket();
        new Thread(saleTicket,"窗口1").start();
        new Thread(saleTicket,"窗口2").start();
        new Thread(saleTicket,"窗口3").start();
        new Thread(saleTicket,"窗口4").start();
        new Thread(saleTicket,"窗口5").start();
    }

    class SaleTicket implements Runnable {
        @Override
        public void run() {
            int num = ticketNum.get();
            while (num > 0) {
                num = ticketNum.getAndDecrement();
                try {
                    if (num > 0) {
                        TimeUnit.MILLISECONDS.sleep(ThreadLocalRandom.current().nextInt(1000, 2000)); // 随机时长,模拟业务逻辑时间
                        System.out.println(Thread.currentThread().getName() + ":  已售出1张票, 还剩余[" + num + "]张票!");
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

通过对比可以发现:使用atomic(无锁编程)效率要明显高于lock方式

3、分布式锁

使用传统Java自带的锁、atomic,锁的有效范围是同一个JVM中的多个线程。当一个系统是多进程,或者多台机器上进行多节点部署时。 需要要从整体上进行锁控制,而不是JVM局部,这就引出了分布式锁。

可以通过Redis实现分布式锁,发挥Redis的高性能和单进程串行的优势。 在多个节点(多个JVM)运行时,不使用JVM内部自带的锁,而是统一都把锁交个Redis来进行。

3.1 实现思路
Redis中有setnx命令,既该命令会判断Redis中是否已经存在key,若存在则set失败。若不存在key,则set成功。我们可以利用此机制来实现分布式加锁。

当需要释放锁(解锁),则可以del删除这个key,这样其他线程就可以set成功。

package org.zyp;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

public class RedisLock {
    private JedisPool jedisPool = null;  // Jedis连接池(如果是Cluster集群,则应该使用JedisCluster)
    private SetParams params = new SetParams().nx().ex(30);  // set的参数。 expire time=30sec

    public RedisLock(JedisPool pool) {
        this.jedisPool = pool;
    }
    //加锁:一直阻塞(反复循环)
    public void lock(String lockID) {
        while( ! tryLock(lockID) ) { // 死循环,直到拿到锁。
            try {
                TimeUnit.MICROSECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //获得锁的尝试
    private boolean tryLock(String lockID) {
        try(Jedis jedis = jedisPool.getResource()) {
            String random_value  = UUID.randomUUID().toString(); // 随机生成作为value
            String result = jedis.set(lockID, random_value, params); // set命令必须在一条语句中,确保原子性
            if("OK".equalsIgnoreCase(result)) {
                return  true;
            }
            return false;
        }
    }
    //释放锁
    public void unLock(String lockID) {
        try(Jedis jedis = jedisPool.getResource()) {
            jedis.del(lockID); // 通过del key 释放锁
        }
    }
}

注意:为何需要设置expire time?

为了避免:某个线程拿到锁后,因某种原因宕掉而未执行unlock释放锁,导致锁长期无法释放,其他现在在申请锁时永远死等待。 通过设置expire time,即使某个已拿到锁的线程宕掉未释放,也有超时机制保障其最终释放。

测试:

package org.zyp;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.JedisPool;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

public class TestDemo {
    private int ticketCount = 10;  // 剩余票数量
    private JedisPool jedisPool = null;
    private RedisLock distributedLock; // 分布式锁
    private final  String LOCK_ID = "lock_ticket";
    public static void main(String[] args) {
        TestDemo testDemo = new TestDemo();
        testDemo.test();
    }

    public void test()  {
        try {
            GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
            poolConfig.setMinIdle(5);
            poolConfig.setMaxTotal(20);
            jedisPool = new JedisPool(poolConfig, "192.168.43.201", 6379);

            TicketsSale ticketssale = new TicketsSale();
            Thread thread1=new Thread(ticketssale,"售票窗口1");
            Thread thread2=new Thread(ticketssale,"售票窗口2");
            Thread thread3=new Thread(ticketssale,"售票窗口3");
            Thread thread4=new Thread(ticketssale,"售票窗口4");
            Thread thread5=new Thread(ticketssale,"售票窗口5");
            thread1.start();
            thread2.start();
            thread3.start();
            thread4.start();
            thread5.start();
            Thread.currentThread().join(); // 主线程一直等待不停。
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    class TicketsSale implements Runnable{
        @Override
        public void run() {
            distributedLock = new RedisLock(jedisPool); // 分布式锁
            while (ticketCount>0){
                try {
                    distributedLock.lock(LOCK_ID);  //加锁
                    if(ticketCount>0){
                        TimeUnit.MILLISECONDS.sleep(ThreadLocalRandom.current().nextInt(1000, 2000)); // 随机时长,模拟业务逻辑时间
                        ticketCount--;
                        System.out.println(Thread.currentThread().getName()+":  已售出1张票, 还剩余["+ticketCount+"]张票!");
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    distributedLock.unLock(LOCK_ID); // 释放锁
                }
            }
        }
    }
}

3.2 存在的问题

上面的RedisLock实现,在每个任务的执行时长大于Redis key的expire time时,则存在问题。例如,若将上面的RedisLock.java代码修改:Redis key的expire time减少为1秒。 这时,小于每个任务的执行时长(每个任务执行时长约为1~2秒)。再次运行发现结果仍会出现超售。因为:

  1. 当线程1还在执行过程中,由于expire time比较短,Redis将其Key删除。
  2. 这时其他线程(比如线程2)可以获得锁(set一个key),但此时线程1还未执行完。
  3. 当线程1执行完后,执行unlock释放锁是,del key动作。 这时del删除的key并不是线程1原来的,而是线程2的。所以导致误删除key。
  4. 这时其他线程(比如线程3)可以获得锁(set一个key),但此时线程2还未执行完。
  5. 其他类似。。。。
  6. 由于key被误删除,整体上未能实现资源同步。

3.3 改进版(避免误删key)

为了避免误删除key,我们可以为每个线程生成不同的value(通过UUDI),只有value属于本线程的,才执行del,否则不删除。

package org.zyp;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;
import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

public class RedisLock {
    private JedisPool jedisPool = null;  // Jedis连接池(如果是Cluster集群,则应该使用JedisCluster)
    private SetParams params = new SetParams().nx().ex(1);  // set的参数。 expire time
    private ThreadLocal<String> threadLocal=new ThreadLocal<>();  // 线程私有变量,仅线程自己可以访问

    public RedisLock(JedisPool pool) {
        this.jedisPool = pool;
    }

    //加锁,一直阻塞(反复循环)
    public void lock(String lockID) {
        while( ! tryLock(lockID) ) { // 死循环,直到拿到锁。
            try {
                TimeUnit.MICROSECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //单词的获得锁的尝试
    private boolean tryLock(String lockID) {
        try(Jedis jedis = jedisPool.getResource()) {
            String random_value  = UUID.randomUUID().toString(); // 随机生成作为value
            String result = jedis.set(lockID, random_value, params); // set命令必须在一条语句中,确保原子性
            if("OK".equalsIgnoreCase(result)) {
                threadLocal.set(random_value); // 获得锁后,将其随机产生的value也保持起来。
                return  true;
            }
            return false;
        }
    }

    //释放锁
    public void unLock(String lockID) throws Exception {
        // lua脚本,先判断value是否相同,只有value相同才执行del,否则不执行del。
        String lua_script="if redis.call(\"get\",KEYS[1])==ARGV[1] then\n" +
                "    return redis.call(\"del\",KEYS[1])\n" +
                "else\n" +
                "    return 0\n" +
                "end";
        try(Jedis jedis = jedisPool.getResource()) {
            Object result = jedis.eval(lua_script, Arrays.asList(lockID), Arrays.asList(threadLocal.get()));  // 为了保证原子性,放在lua脚本中一次执行。
            if(Integer.valueOf(result.toString())==0){
                throw new Exception("解锁失败"); // 抛出这个异常是为了让调用方的事务回滚
            }
        }
    }
}

测试代码TestDemo不变。 多次执行,可以发现:超售问题发生的概率,已经大幅度降低。但仍有超时发生。因为:只是避免了误删除key,但是仍存在多个线程同时进入同步资源代码(续租问题仍然存在)

3.4 改进版(续租)

只要保证:获得锁业务线程的执行时长(持有锁时长)大于Redis中key的expire time,就不会有问题了。既不断的给Redis中key的expire time延期,确保它的时长大于线程的执行时长。

对于每个lockID,分别启动一个对应的watchdog线程。 watchdog线程不断的查看ttl剩余时长,如未过期的key则进行延期。这里仍有一个问题:万一获得锁业务线程崩溃,watchdog仍在不停延时,这仍存在问题。 解决这个问题的办法是将watchdog设置为守护线程,当业务线程崩溃停止只剩下守护线程时,Java会自动全部停止

改进代码如下:

package org.zyp;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;
import java.util.ArrayList;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

public class RedisLock {
    private JedisPool jedisPool = null;  // Jedis连接池(如果是Cluster集群,则应该使用JedisCluster)
    private SetParams params = new SetParams().nx().ex(1);  // set的参数。 expire time
    private ArrayList<String> watch_lockid_set = new ArrayList<String>(); // 存放已被watchdog监控的lockid列表

    public RedisLock(JedisPool pool) {
        this.jedisPool = pool;
    }

    //加锁。一直阻塞(反复循环)
    public void lock(String lockID) {
        while( ! tryLock(lockID) ) { // 死循环,直到拿到锁。
            try {
                TimeUnit.MICROSECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //单词的获得锁的尝试
    private boolean tryLock(String lockID) {
        try(Jedis jedis = jedisPool.getResource()) {
            String random_value  = UUID.randomUUID().toString(); // 随机生成作为value
            String result = jedis.set(lockID, random_value, params); // set命令必须在一条语句中,确保原子性
            if("OK".equalsIgnoreCase(result)) {
                if( ! watch_lockid_set.contains(lockID)) { // list没有包含,则说明是第一次,则需要开启watchdog(既对于同一个lockid,只需要启动一个watchdog)
                    Thread t = new Thread(new LockWatchLog(jedisPool, lockID), "LockWatchLog-"+ lockID)
                    t.setDaemon(true)
                    t.start(); // 启动监控watchdog
                    watch_lockid_set.add(lockID);
                }
                return  true;
            }
            return false;
        }
    }
    //释放锁
    public void unLock(String lockID) throws Exception {
        // lua脚本,先判断value是否相同,只有value相同才执行del,否则不执行del。
        String lua_script="if redis.call(\"get\",KEYS[1])==ARGV[1] then\n" +
                "    return redis.call(\"del\",KEYS[1])\n" +
                "else\n" +
                "    return 0\n" +
                "end";
        try(Jedis jedis = jedisPool.getResource()) {
            Object result = jedis.eval(lua_script, Arrays.asList(lockID), Arrays.asList(threadLocal.get()));  // 为了保证原子性,放在lua脚本中一次执行。
            if(Integer.valueOf(result.toString())==0){
                throw new Exception("解锁失败"); // 抛出这个异常是为了让调用方的事务回滚
            }
        }
    }
    // 内部类,定义一个守护线程。不断的给仍存在的key(未被del)延长时间
    class LockWatchLog implements Runnable{
        private JedisPool jedisPool;
        private String watch_lockid = null;
        public LockWatchLog(JedisPool jedisPool, String watch_lockid){
            this.jedisPool = jedisPool;
            this.watch_lockid = watch_lockid;
        }
        @Override
        public void run() {
            Jedis jedis = jedisPool.getResource();
            while (true){
                Long current_ttl = jedis.ttl(watch_lockid); // 获得剩余的ttl
                if( current_ttl != null && current_ttl > 0){ // key还没有过期。
                    jedis.expire(watch_lockid, (int) (current_ttl+1)); // 则延期
                    try {
                        TimeUnit.MILLISECONDS.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else { // 否则
                    watch_lockid_set.remove(watch_lockid);  // 移除
                    break; // 退出循环,结束本watchdog线程。
                }
            }
        }
    }
}

测试代码TestDemo不变。 多次执行,可以发现不会出现超售。

4、Redisson

其实在Redis官方文档中,有专门介绍分布式锁内容:Distributed locks with Redis(https://redis.io/topics/distlock)其中Java语言的实现是Redisson, 该Redisson的文档为(https://github.com/redisson/redisson/wiki)该RedissonRedLock的基本原理如同前文中的改进方案,也是使用了一个watchdog不断的延长锁的有效期。

同时,前文例子中都是单机版Redis服务,实际环境中Redis为多机(包括读写分离、Cluster等等)。RedissonRedLock的功能比较强大,它支持Redis Server为多节点,包括主从复制、包括Cluster机器。此外,Redisson除了支持支持可重入锁,还支持公平锁Fair Lock、MultiLock、ReadWriteLock、Semaphore、PermitExpirableSemaphore、CountDownLatch。

  • 如果已获取锁的Redisson实例崩溃,则该锁无法解锁而永久挂起,为避免此此问题,可以通过设置超时时间来实现。
  • 为了解决续租问题,Redisson维护锁看门狗,它会在锁持有人Redisson实例处于活动状态时延长锁的到期时间。默认情况下,锁看门狗超时为30秒,可以通过Config.lockWatchdogTimeout设置进行更改。
  • Redisson还允许leaseTime在锁定获取期间指定参数。在指定的时间间隔后,锁定的锁将自动释放。

基于Redis的分布式可重入Lock对象,实现了java.util.concurrent.locks.Lock接口。RLock对象的行为符合Java Lock规范。这意味着只有锁拥有者线程才能解锁它,否则IllegalMonitorStateException将被抛出。否则考虑使用RSemaphore对象。

基于redission的分布式锁代码如下:

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

代码: 

package org.zyp;

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

public class TestDemo {
    private int ticketCount = 10;  // 剩余票数量
    private RedissonClient redisson = null;
    private final  String LOCK_ID = "lock_ticket";
    public static void main(String[] args) {
        TestDemo testDemo = new TestDemo();
        testDemo.test();
    }

    public void test()  {
        try {
            Config config = new Config();
            config.useSingleServer().setAddress("redis://192.168.43.201:6379");
            redisson = Redisson.create(config);

            TicketsSale ticketssale = new TicketsSale();
            Thread thread1=new Thread(ticketssale,"售票窗口1");
            Thread thread2=new Thread(ticketssale,"售票窗口2");
            Thread thread3=new Thread(ticketssale,"售票窗口3");
            Thread thread4=new Thread(ticketssale,"售票窗口4");
            Thread thread5=new Thread(ticketssale,"售票窗口5");
            thread1.start();
            thread2.start();
            thread3.start();
            thread4.start();
            thread5.start();
            Thread.currentThread().join(); // 主线程一直等待不停。
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    class TicketsSale implements Runnable{
        @Override
        public void run() {
            RLock rLock = redisson.getLock(LOCK_ID);; // 使用Redisson分布式锁
            while (ticketCount>0){
                try {
                    rLock.lock(10, TimeUnit.SECONDS);  // 加锁 acquire lock and automatically unlock it after 10 seconds
                    if(ticketCount>0){
                        TimeUnit.MILLISECONDS.sleep(ThreadLocalRandom.current().nextInt(1000, 2000)); // 随机时长,模拟业务逻辑时间
                        ticketCount--;
                        System.out.println(Thread.currentThread().getName()+":  已售出1张票, 还剩余["+ticketCount+"]张票!");
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    try {
                        rLock.unlock(); // 释放锁
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

多次运行结果,可以发现:

  • 如果rlock.lock()设置的自动过期时间比较长(大于业务执行时长),则没有超售现象。
  • 如果rlock.lock()设置的自动过期时间比较短,仍会有超时现象。且报异常:java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: cda3b92a-f89c-46c1-834e-25d5ca134cb6 thread-id: 47

为什么存在超售且有异常呢? 网上有很多文章讲是lockInterruptibly的问题,其说法是错误的。

真正的原因是:

  1. 问题一:如果lock()方法的入参,如果显示指定了leaseTime(自动过期时长),则watchdog机制不会生效。
  2. 问题二:如果lock()没有指定入参,则watchdog生效。当LockWatchdogTimeout设置过短(小于业务执行时长)仍会存在问题,LockWatchdogTimeout定义了watchdog看门狗的存活时长,超过时长则不在“看门”(不在执行锁续期),因此LockWatchdogTimeout的时长应该设置长一点(大于所有业务的执行时间),LockWatchdogTimeout设置的长一点,几乎没有负作用
  3. 另外:unlock()方法执行前可以增加判断if(rLock.isLocked() && rLock.isHeldByCurrentThread()),只有if条件满足才执行unlock()动作。虽然有个这个判断可避免报异常java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id。 但个人强烈不建议增加此判断(官方的代码也无此判断),因为报此异常通常不是unlock的问题而是程序有问题,应该从根本上去分析解决。而加上这个if判断后,容易把存在程序问题掩盖,难以第一时间发现诊断出问题

修改TestDemo.java,改动两处:

  • 代码中显示设置config.setLockWatchdogTimeout()的时长
  • 使用无参的rLock.lock()方法,使watchdog生效
package org.zyp;

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

public class TestDemo {
    private int ticketCount = 10;  // 剩余票数量
    private RedissonClient redisson = null;
    private final  String LOCK_ID = "lock_ticket";
    public static void main(String[] args) {
        TestDemo testDemo = new TestDemo();
        testDemo.test();
    }

    public void test()  {
        try {
            Config config = new Config();
            config.useSingleServer().setAddress("redis://192.168.43.201:6379");
            config.setLockWatchdogTimeout(60000); // 确保LockWatchdogTimeout足够长
            redisson = Redisson.create(config);

            TicketsSale ticketssale = new TicketsSale();
            Thread thread1=new Thread(ticketssale,"售票窗口1");
            Thread thread2=new Thread(ticketssale,"售票窗口2");
            Thread thread3=new Thread(ticketssale,"售票窗口3");
            Thread thread4=new Thread(ticketssale,"售票窗口4");
            Thread thread5=new Thread(ticketssale,"售票窗口5");
            thread1.start();
            thread2.start();
            thread3.start();
            thread4.start();
            thread5.start();
            Thread.currentThread().join(); // 主线程一直等待不停。
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    class TicketsSale implements Runnable{
        @Override
        public void run() {
            RLock rLock = redisson.getLock(LOCK_ID);; // 分布式锁
            while (ticketCount>0){
                try {
                    //rLock.lock(1, TimeUnit.SECONDS);  // 加锁(watchdog不生效) acquire lock and automatically unlock it after 10 seconds
                    rLock.lock();  // 加锁(watchdog生效)
                    if(!Thread.currentThread().isInterrupted() && ticketCount>0){
                        TimeUnit.MILLISECONDS.sleep(ThreadLocalRandom.current().nextInt(1000, 2000)); // 随机时长,模拟业务逻辑时间
                        ticketCount--;
                        System.out.println(Thread.currentThread().getName()+":  已售出1张票, 还剩余["+ticketCount+"]张票!");
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    try {
                        rLock.unlock(); // 释放锁
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

参考:https://blog.csdn.net/zyplanke/article/details/107118895

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赶路人儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值