锁的原理和使用场景,乐观锁、悲观锁、公平锁、非公平锁,基于数据库、Redis、Zookeeper实现分布式锁的原理及代码实现

一、锁

1.1 什么是锁?

在JAVA中是一个非常重要的概念,尤其是在当今的互联网时代,高并发的场景下,更是离不开锁。那么锁到底是什么呢?在计算机科学中,锁(lock)或互斥(mutex)是一种同步机制,用于在有许多执行线程的环境中强制对资源的访问限制。锁旨在强制实施互斥排他、并发控制策略。
举一个生活中的例子:大家都去过超市买东西,如果你随身带了包呢,要放到储物柜里。咱们把这个例子再极端一下,假如柜子只有一个,现在同时来了3个人A,B,C,都要往这个柜子里放东西。这个场景就构造了一个多线程,多线程自然离不开锁。如下图所示:
在这里插入图片描述
A,B,C都要往柜子里放东西,可是柜子只能放一件东西,那怎么办呢?这个时候呢就引出了锁的概念,3个人中谁抢到了柜子的锁,谁就可以使用这个柜子,其他的人只能等待。比如:C抢到了锁,C可以使用这个柜子。A和B只能等待,等C使用完了,释放锁以后,A和B再争抢锁,谁抢到了,再继续使用柜子。

1.2 代码演示

  • 创建柜子Cabinet类

      public class Cabinet {
      	//柜子中存储的数字
          private int storeNumber;
          public int getStoreNumber() {
              return storeNumber;
          }
          public void setStoreNumber(int storeNumber) {
              this.storeNumber = storeNumber;
          }
      }
    
  • 创建用户UserInfo类,其中

      public class UserInfo {
      	//柜子
          private Cabinet cabinet;
      	 //存储的数字
          private int storeNumber;
      
          public UserInfo(Cabinet cabinet, int storeNumber) {
              this.cabinet = cabinet;
              this.storeNumber = storeNumber;
          }
      
          public void useCabinet(){
              cabinet.setStoreNumber(storeNumber);
          }
      }
    
  • 启动类模拟3个用户使用柜子的场景:

      public class Starter {
      
          public static void main(String[] args) {
              final Cabinet cabinet = new Cabinet();
              ExecutorService es = Executors.newFixedThreadPool(3);
              for (int i = 0; i < 3; i++) {
                  final int storeNumber = i;
                  es.execute(() -> {
                      UserInfo users = new UserInfo(cabinet, storeNumber);
                      synchronized (cabinet) {
                          users.useCabinet();
                          System.out.println("我是用户" + storeNumber + ",我存储的数字是:" + cabinet.getStoreNumber());
                      }
                  });
              }
              es.shutdown();
          }
      }
    

1.当柜子没有加锁的时候,3个用户并行的执行,向柜子中存储了他们的数字,虽然3个用户并行的同时操作,但在具体赋值的时候也是有顺序的,因为setStoreNumber只占有一块内存,storeNumber只存储最后的线程所设置的值,至于那个线程排在最后是不确定的。所以在打印storeNumber取值时,3个线程取到的值是相同的。

2.如果在设置storeNumber的方法上加上synchronized关键字,这样在存储数字的时候,就不会并行的去执行了,而是哪个用户抢到锁,哪个用户执行存储数字的方法。但是由于存储语句和打印方法是两个语句,并没有保证原子性,虽然在set方法上加了锁,但是在打印的时候又会产生一个并发,打印语句是有锁的,但是不能确定哪个线程去执行。所以这里,我们要保证useCabinet和打印的方法的原子性,就必须使用synchronized块,并且由于每个线程都初始化了user,总共有3个user对象了,而cabinet对象只有一个,我们使用synchronized块里的对象要用cabinet。

具体图例如下:
在这里插入图片描述

1.3 常用锁

JAVA为我们提供了种类丰富的锁,每种锁都有不同的特性,锁的使用场景也各不相同。在这里会通过锁的定义,核心代码剖析,以及使用场景来给大家介绍JAVA中主流的几种锁。

public class Test {
    private int i=0;
    public static void main(String[] args) {
        Test test = new Test();
        ExecutorService es = Executors.newFixedThreadPool(50);
        CountDownLatch cdl = new CountDownLatch(5000);
        for (int i = 0;i < 5000; i++){
            es.execute(()->{
                test.i++;
                cdl.countDown();
            });
        }
        es.shutdown();
        try {
            //等待5000个任务执行完成后,打印出执行结果
            cdl.await();
            System.out.println("执行完成后,i="+test.i);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

上面的程序中,我们模拟了50个线程同时执行i++,总共执行5000次,按照常规的理解,得到的结果应该是5000,我们运行一下程序,看看执行的结果如何?

执行完成后,i=4975
执行完成后,i=4986
执行完成后,i=4971

这是我们运行3次以后得到的结果,可以看到每次执行的结果都不一样,而且不是5000,这是为什么呢?这就说明i++并不是一个原子性的操作,在多线程的情况下并不安全。我们把i++的详细执行步骤拆解一下:

  • 从内存中取出i的当前值;
  • 将i的值加1;
  • 将计算好的值放入到内存当中;

在多线程的场景下,我们可以想象一下,线程A和线程B同时从内存取出i的值,假如i的值是1000,然后线程A和线程B再同时执行+1的操作,然后把值再放入内存当中,这时,内存中的值是1001,而我们期望的是1002,正是这个原因导致了上面的错误。那么我们如何解决呢?

1.3.1 乐观锁

乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读-比较-写的操作。

java中的乐观锁一般会使用“数据版本机制”或“CAS操作”来实现。

  • 实现数据版本一般有两种,第一种是使用版本号,第二种是使用时间戳。以版本号方式为例。
      
    版本号方式:一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
      
    核心SQL代码:

    update table set xxx=#{xxx}, version=version+1 where id=#{id} and version=#{version};

  • CAS操作

    CAS(Compare and Swap 比较并交换),当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
      
    CAS操作中包含三个操作数——需要读写的内存位置(V)、进行比较的预期原值(A)和拟写入的新值(B)。如果内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置值更新为新值B,否则处理器不做任何操作。
    在JAVA1.5以后,JDK官方提供了大量的原子类,这些类的内部都是基于CAS机制的,也就是使用了乐观锁。

我们将上面的程序稍微改造一下,如下:

public class Test {
	private AtomicInteger i = new AtomicInteger(0);
    public static void main(String[] args) {
        Test test = new Test();
        ExecutorService es = Executors.newFixedThreadPool(50);
        CountDownLatch cdl = new CountDownLatch(5000);
        for (int i = 0;i < 5000; i++){
            es.execute(()->{
                test.i.incrementAndGet();
                cdl.countDown();
            });
        }
        es.shutdown();
        try {
            cdl.await();
            System.out.println("执行完成后,i="+test.i);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

我们将变量i的类型改为AtomicIntegerAtomicInteger是一个原子类。我们在之前调用i++的地方改成了i.incrementAndGet()incrementAndGet()方法采用了CAS机制,也就是说使用了乐观锁。

1.3.2 悲观锁

悲观锁与乐观锁恰恰相反,悲观锁从读取数据的时候就加了锁,而且在更新数据的时候,保证只有一个线程在执行更新操作,在这期间只能有一个线程去操作,其他的线程只能等待。没有像乐观锁那样进行数据版本的比较。所以悲观锁适用于读相对少,写相对多的操作。

在JAVA中,悲观锁可以使用synchronized关键字或者ReentrantLock类来实现。还是上面的例子,我们分别使用这两种方式来实现一下。

首先是使用synchronized关键字来实现:

public class Test {
    private int i=0;
    public static void main(String[] args) {
        Test test = new Test();
        ExecutorService es = Executors.newFixedThreadPool(50);
        CountDownLatch cdl = new CountDownLatch(5000);
        for (int i = 0;i < 5000; i++){
            es.execute(()->{
                //修改部分  开始
                synchronized (test){
                    test.i++;
                }
                //修改部分  结束
                cdl.countDown();
            });
        }
        es.shutdown();
        try {
            cdl.await();
            System.out.println("执行完成后,i="+test.i);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

接下来,我们再使用ReentrantLock类来实现悲观锁。代码如下:

public class Test {
    //添加了ReentrantLock锁
    Lock lock = new ReentrantLock();
    private int i=0;
    public static void main(String[] args) {
        Test test = new Test();
        ExecutorService es = Executors.newFixedThreadPool(50);
        CountDownLatch cdl = new CountDownLatch(5000);
        for (int i = 0;i < 5000; i++){
            es.execute(()->{
                //加锁
                test.lock.lock();
                test.i++;
                //释放锁
                test.lock.unlock();
                cdl.countDown();
            });
        }
        es.shutdown();
        try {
            cdl.await();
            System.out.println("执行完成后,i="+test.i);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

我们在类中显示的增加了Lock lock = new ReentrantLock();,而且在i++之前增加了lock.lock(),加锁操作,在i++之后增加了lock.unlock()释放锁的操作。

1.3.2 公平锁和非公平锁

  • 公平锁是指多个线程按照申请锁的顺序来获取锁。

  • 非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。非公平锁的优点在于吞吐量比公平锁大。

对于Java ReetrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。

对于Synchronized而言,也是一种非公平锁。

公平锁与非公平锁都在ReentrantLock类里给出了实现,我们看一下ReentrantLock的源码。

public ReentrantLock() {
    sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

ReentrantLock有两个构造方法,默认的构造方法中,sync = new NonfairSync();我们可以从字面意思看出它是一个非公平锁。再看看第二个构造方法,它需要传入一个参数,参数是一个布尔型,true是公平锁,false是非公平锁。从上面的源码我们可以看出sync有两个实现类,分别是FairSyncNonfairSync,我们再看看获取锁的核心方法,首先是公平锁FairSync的:

@ReservedStackAccess
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

然后是非公平锁NonfairSync的:

@ReservedStackAccess
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

通过对比两个方法,我们可以看出唯一的不同之处在于!hasQueuedPredecessors()这个方法,很明显这个方法是一个队列,由此可以推断,公平锁是将所有的线程放在一个队列中,一个线程执行完成后,从队列中取出下一个线程,而非公平锁则没有这个队列。这些都是公平锁与非公平锁底层的实现原理,我们在使用的时候不用追到这么深层次的代码,只需要了解公平锁与非公平锁的含义,并且在调用构造方法时,传入truefalse即可。

二、分布式锁

在说分布式锁之前,我们看一看单体应用锁的特点,单体应用锁是在一个JVM进程内有效,无法跨JVM、跨进程。那么分布式锁的定义就出来了,分布式锁就是可以跨越多个JVM、跨越多个进程的锁,这种锁就叫做分布式锁。
在这里插入图片描述
分布式锁都是通过第三方组件来实现的,目前比较流行的分布式锁的解决方案有:

  • 数据库,通过数据库可以实现分布式锁,但是在高并发的情况下对数据库压力较大,所以很少使用。
  • Redis,借助Redis也可以实现分布式锁,而且Redis的Java客户端种类很多,使用的方法也不尽相同。
  • Zookeeper,Zookeeper也可以实现分布式锁,同样Zookeeper也存在多个Java客户端,使用方法也不相同。

具体实现方法的代码实现及优缺点如下:

方式优点缺点
数据库实现简单、易于理解对数据库压力大
Redis易于理解 自己实现不支持阻塞
Zookeeper支持阻塞需理解Zookeeper、程序复杂
Curator(推荐)提供锁的方法依赖Zookeeper、强一致
Redisson(推荐)提供锁的方法,可阻塞

2.1 基于数据库的分布式锁

2.1.1原理

  • 多个进程、多个线程访问共同组建数据库
  • 通过select…for update访问同一条数据
  • for updatet锁定数据,其他线程只能等待

2.1.2 代码

//实例
@Data
public class DistributeLock {
    private Integer id;
    private String businessCode;
    private String businessName;
}

//api接口
@Slf4j
@RestController
public class DemoController {
    @GetMapping("singleLock")
    @Transactional(rollbackOn = Exception.class)
    public String singleLock() throws Exception {
        log.info("我进入了方法");
        DistributeLock distributeLock = distributeLockMapper.selectDistributeLock("demo");
        if (distributeLock == null) {
            throw new Exception("分步式锁找不到");
        }
        log.info("我进入了锁");
        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "执行完成";
    }
}

//DistributeLockMapper.xml配置类中添加如下sql语句
<select id="selectDistributeLock" resultType="com.example.distributelock.model.DistributeLock">
    select * from distribute_lock where business_code = #{businessCode,jdbcType=VARCHAR} for update
</select>

//DistributeLockMapper文件中添加selectDistributeLock方法,和上述sql中id同名
@Component
public interface DistributeLockMapper {
	    DistributeLock selectDistributeLock(@Param("businessCode")String businessCode);
}

//在application.yml中添加如下配置,mapper类和mapper.xml不在同一个路径下时,用mapper-locations指定mapper.xml的路径
mybatis:
  mapper-locations: /mybatis/*.xml

2.2 基于redis的setnx实现分布式锁

2.2.1 原理

获取锁的redis命令 set resource_name my_random_value NX PX 30000

  • resource_name: 资源名称,可根据不同的业务区分不同的锁
  • my_random_value:随机数,每个线程的随机值都不同,用于释放锁时的校验
  • NX: key不存在时设置成功,key存在时设置不成功
  • PX: 自动失效时间,出现异常情况,锁可以过期失效

利用NX的原子性,多个线程并发时,只有一个线程可以设置成功。设置成功及获得锁,可以执行后续的业务处理,如果出现了异常,过了锁的有效期,锁会自动释放。释放锁采用Redis的delete命令,释放锁时校验之前设置的随机数,相同才能释放。释放锁采用LUA脚本的方式实现。具体的流程如下图:
在这里插入图片描述

2.2.2 代码

//实现类
@Slf4j
public class RedisLock implements AutoCloseable {

    private RedisTemplate redisTemplate;
    private String key;
    private String value;
    private int expireTime;

    public RedisLock(RedisTemplate redisTemplate, String key, int expireTime) {
        this.redisTemplate = redisTemplate;
        this.key = key;
        this.value = UUID.randomUUID().toString();
        this.expireTime = expireTime;
    }

    public boolean getLock() {
        RedisCallback<Boolean> redisCallback = connection -> {
            RedisStringCommands.SetOption setOption = RedisStringCommands.SetOption.ifAbsent();
            Expiration expiration = Expiration.seconds(expireTime);
            byte[] redisKey = redisTemplate.getKeySerializer().serialize(key);
            byte[] redisValue = redisTemplate.getValueSerializer().serialize(value);
            Boolean result = connection.set(redisKey, redisValue, expiration, setOption);
            return result;
        };
        return (Boolean) redisTemplate.execute(redisCallback);
    }

    public boolean unlock() {
        String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
                "    return redis.call(\"del\",KEYS[1])\n" +
                "else\n" +
                "    return 0\n" +
                "end";
        RedisScript<Boolean> redisScript = RedisScript.of(script, Boolean.class);
        List<String> keys = Arrays.asList(key);
        Boolean result = (Boolean) redisTemplate.execute(redisScript, keys, value);
        log.info("释放锁的结果:"+result);
        return result;
    }

    @Override
    public void close() throws Exception {
        unlock();
    }
}

//api
@RestController
@Slf4j
public class RedisLockController {

    @Autowired
    private RedisTemplate redisTemplate;

    @GetMapping("redisLock")
    public String redisLock() {
        log.info("我进入了方法!");
        try (RedisLock redisLock = new RedisLock(redisTemplate, "redisKey", 30)) {
            if (redisLock.getLock()) {
                log.info("我进入了锁");
                Thread.sleep(15000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.info("方法执行完成");
        return "方法执行完成";
    }
}

2.2.3 基于分布式锁解决定时任务重复问题

1.在启动类上添加@EnableScheduling注解
2.使用@Scheduled注解,开启定时任务

@Service
@Slf4j
public class SchedulerService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Scheduled(cron = "0/5 * * * * ?")
    public void sendSms() {
        try (RedisLock redisLock = new RedisLock(redisTemplate, "autoSms", 30)) {
            if (redisLock.getLock()) {
                log.info("向138xxxxxxxx发送短信!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2.3 zookeeper分布式锁代码实现

2.3.1 原理

利用zookeeper瞬时有序节点的特性:

  1. 多线程并发创建瞬时节点时,得到有序的序列。
  2. 序号最小的线程获得锁,其他线程监听自己序号的前一个序号。
  3. 前一个线程执行完成,删除自己序号的节点。
  4. 下一个序号的线程得到通知,继续执行。
  5. 以此类推。
  6. 创建节点时,已经确定了线程的执行顺序。

2.3.2 代码

1.使用docker启动zookeeper
docker run -d --name=zoo4 -p 2181:2181 -v /mydata/zookeeper/zoo4/data:/data -v /mydata/zookeeper/zoo4/datalog:/datalog zookeeper

在这里插入图片描述
2.实现类

@Slf4j
public class ZkLock implements AutoCloseable, Watcher {

    private ZooKeeper zooKeeper;

    private String zNode;

    public ZkLock() throws IOException {
        this.zooKeeper = new ZooKeeper("47.94.93.93:2181", 10000, this);
    }

    public boolean getLock(String businessCode) {
        try {
            //创建业务 根节点
            Stat stat = zooKeeper.exists("/" + businessCode, false);
            if (stat == null) {
                zooKeeper.create("/" + businessCode,
                        businessCode.getBytes(),
                        ZooDefs.Ids.OPEN_ACL_UNSAFE,
                        CreateMode.PERSISTENT);
            }

            //创建瞬时有序节点
            zNode = zooKeeper.create("/" + businessCode + "/" + businessCode + "_",
                    businessCode.getBytes(),
                    ZooDefs.Ids.OPEN_ACL_UNSAFE,
                    CreateMode.EPHEMERAL_SEQUENTIAL);

            //获取业务节点下所有的子节点
            List<String> childrenNodes = zooKeeper.getChildren("/" + businessCode, false);
            //子节点排序
            Collections.sort(childrenNodes);
            //获取序号最小的(第一个)节点
            String firstNode = childrenNodes.get(0);
            //如果创建的节点是第一个子节点,则获得锁
            if (zNode.endsWith(firstNode)) {
                return true;
            }

            //如果不是第一个节点,则监听前一个节点
            String lastNode = firstNode;
            for (String node : childrenNodes) {
                if (zNode.endsWith(node)) {
                    zooKeeper.exists("/" + businessCode + "/" + lastNode, true);
                    break;
                } else {
                    lastNode = node;
                }
            }

            synchronized (this) {
                wait();
            }
            return true;
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }


    @Override
    public void close() throws Exception {
        zooKeeper.delete(zNode, -1);
        zooKeeper.close();
        log.info("我已经释放了锁");
    }

    @Override
    public void process(WatchedEvent watchedEvent) {
        if (watchedEvent.getType() == Event.EventType.NodeDeleted) {
            synchronized (this) {
                notify();
            }
        }
    }
}

3.api

@RestController
@Slf4j
public class ZookeeperController {
	@RequestMapping("zkLock")
	public String zookeeperLock(){
        log.info("进入了方法!");
        try(ZkLock zkLock = new ZkLock()){
            if(zkLock.getLock("order")){
                log.info("我获取了锁");
                Thread.sleep(10000);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.info("方法执行完成");
        return "方法执行完成";
    }
}

4.maven

<dependency>
     <groupId>org.apache.zookeeper</groupId>
      <artifactId>zookeeper</artifactId>
      <version>3.4.14</version>
  </dependency>

2.4 基于curator分布式锁

2.4.1 原理

  • Curator实质上是基于Zookeeper实现的分布式锁方案。
  • 引入Curator客户端,Curator已经实现了分布式锁的方法,使用的时候直接调用即可

2.4.2 代码

1.启动类

@SpringBootApplication
public class DistributeZkLockApplication {

    public static void main(String[] args) {
        SpringApplication.run(DistributeZkLockApplication.class, args);
    }

    @Bean(initMethod="start",destroyMethod = "close")
    public CuratorFramework getCuratorFramework() {
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        CuratorFramework client = CuratorFrameworkFactory.newClient("47.94.93.93:2181", retryPolicy);
        return client;
    }
}

2.api

@RestController
@Slf4j
public class ZookeeperController {
	@RequestMapping("curatorLock")
    public String curatorLock(){
        log.info("我进入了方法!");
        InterProcessMutex lock = new InterProcessMutex(client, "/order");
        try {
            if (lock.acquire(30, TimeUnit.SECONDS)) {
                log.info("我获得了锁!!");
                Thread.sleep(10000);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                log.info("我释放了锁!!");
                lock.release();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        log.info("方法执行完成!");
        return "方法执行完成!";
    }
}

3.maven

<dependency>
      <groupId>org.apache.zookeeper</groupId>
       <artifactId>zookeeper</artifactId>
       <version>3.4.14</version>
   </dependency>
   <dependency>
       <groupId>org.apache.curator</groupId>
       <artifactId>curator-recipes</artifactId>
       <version>4.2.0</version>
   </dependency>

2.5 基于Redisson实现分布式锁

2.5.1 Redisson简介

Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid)。

Redisson主要适用于以下几种场景:
分布式应用,缓存,分布式会话,分布式任务/服务/延迟执行服务,Redis客户端

Redisson在基于NIO的Netty框架上,充分的利用了Redis键值数据库提供的一系列优势,在Java实用工具包中常用接口的基础上,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作。

2.5.2 代码

1.application.yml文件

spring:
  redis:
    host: 47.94.93.93
    port: 6379
    password: 123456
    database: 2

2.api

@RestController
@Slf4j
public class RedissonLockController {

    @Autowired
    private RedissonClient redissonClient;

    @RequestMapping("redissonLock")
    public String redissonLock() {
        RLock rLock = redissonClient.getLock("order");
        try {
            rLock.lock(30, TimeUnit.SECONDS);
            log.info("我获得了锁");
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            log.info("我释放了锁");
            rLock.unlock();
        }
        log.info("方法执行完成");
        return "方法执行完成";
    }
}

3.maven

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.12.0</version>
</dependency>
  • 29
    点赞
  • 208
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
回答: Redis分布式锁适用于多个应用实例之间需要协调互斥访问同一个资源的场景。它可以帮助我们控制分布式系统中对共享资源的访问,避免并发问题和重复操作。常见的使用场景包括: 1. 分布式任务调度:多个应用实例需要定时执行某个任务,通过使用分布式锁可以确保只有一个实例执行任务,避免重复执行。 2. 分布式缓存更新:多个应用实例需要同时更新某个缓存数据,通过使用分布式锁可以保证只有一个实例进行更新,避免数据不一致。 3. 分布式资源竞争:多个应用实例需要竞争某个资源,例如分布式锁可以用于实现分布式限流、分布式排他性操作等场景。 需要注意的是,分布式锁实现需要考虑到高并发、死、误删等问题,需要根据具体的应用场景进行优化和测试。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* *3* [Redis 分布式锁实现原理和应用场景](https://blog.csdn.net/weixin_43025343/article/details/131081958)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Redis实现分布式锁及其应用场景](https://blog.csdn.net/Crime11/article/details/130132324)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值