文章目录

  • 前言
  • 一、基于数据库实现分布式锁
  • 二、Redisson实现分布式锁
  • 三、Zookeeper实现分布式锁
  • 四、总结

前言

目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。”所以,很多系统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。

在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。有的时候,我们需要保证一个方法在同一时间内只能被同一个线程执行。

分布式锁有三种实现方式:
1.基于数据库实现分布式锁;
2.基于缓存(Redis等)实现分布式锁;
3.基于Zookeeper实现分布式锁;

本篇只是基于单JVM做测试。


一、基于数据库实现分布式锁

  1. 建表语句
    其中lock_key为锁的名称,并设置唯一索引。
CREATE TABLE `distribute_lock` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,`lock_key` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,PRIMARY KEY (`id`),UNIQUE KEY `lock_key` (`lock_key`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
  1. 实体类
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;import java.io.Serializable;@TableName("distribute_lock")
@Data
public class DistributeLock implements Serializable {@TableId(value = "id", type = IdType.AUTO)private Long id;@TableField("lock_key")private String lockKey;
}

3. 测试
此处数据库相关操作使用Mybatis-Plus实现。

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.entity.DistributeLock;
import com.service.LockService;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class MySQLDistributedLock {@Autowiredprivate LockService lockService;//锁名称private static String LOCK_KEY = "lock_key";@Testpublic void testLock() throws InterruptedException {new Thread(new Runnable() {@Overridepublic void run() {boolean flag = false;try {DistributeLock lock = new DistributeLock();lock.setLockKey(LOCK_KEY);flag = lockService.save(lock);if (flag) {log.info(Thread.currentThread().getName() + ":线程1获取锁成功");//模拟业务场景Thread.sleep(3000);} } catch (Exception e) {log.error(Thread.currentThread().getName() + ":线程1获取锁异常");} finally {//只有抢锁成功才能释放锁,防止将其他线程持有的锁释放if (flag) {lockService.remove(Wrappers.<DistributeLock>lambdaQuery().eq(DistributeLock::getLockKey, LOCK_KEY));log.info(Thread.currentThread().getName() + ":线程1释放锁成功");}}}}).start();new Thread(new Runnable() {@Overridepublic void run() {boolean flag = false;try {DistributeLock lock = new DistributeLock();lock.setLockKey(LOCK_KEY);flag = lockService.save(lock);if (flag) {log.info(Thread.currentThread().getName() + ":线程2获取锁成功");//模拟业务场景Thread.sleep(3000);} } catch (Exception e) {log.error(Thread.currentThread().getName() + ":线程2获取锁异常");} finally {//只有抢锁成功才能释放锁,防止将其他线程持有的锁释放if (flag) {lockService.remove(Wrappers.<DistributeLock>lambdaQuery().eq(DistributeLock::getLockKey, LOCK_KEY));log.info(Thread.currentThread().getName() + ":线程2释放锁成功");}}}}).start();//睡眠10秒钟,防止主线程执行完关闭程序,而其他线程还未执行Thread.sleep(10000);}
}

4. 输出结果
结果显示线程2先抢到锁,即先插入一条数据到数据库中,此时线程1再去插表时因为唯一索引导致插表失败,即抢锁失败,这种方式实现分布式锁时不可重入的。

二、Redisson实现分布式锁

1. 依赖

<!--redisson-->
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.8.2</version>
</dependency>

2. 实体类

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;import java.io.Serializable;@TableName("distribute_lock")
@Data
public class DistributeLock implements Serializable {@TableId(value = "id", type = IdType.AUTO)private Long id;@TableField("lock_key")private String lockKey;
}

3. 测试

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;import java.util.concurrent.TimeUnit;public class RedisDistributedLock {public static void main(String[] args) throws InterruptedException {//单机模式下构造redisson必须的配置Config config = new Config();config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);RedissonClient redissonClient = Redisson.create(config);new Thread(new Runnable() {@Overridepublic void run() {//此处多线程获取锁的名称必须相同,代表获取同一把锁RLock lock = redissonClient.getLock("lock");try {/*** param1:waitTime:获取锁的最大等待时长,超过此时长代表抢锁失败,此处表示抢锁超过3秒即代表抢锁失败* param2:leaseTime:续约时间,代表持锁的最大时长,此处表示持锁10秒后,无论程序是否运行完毕都必须释放锁,主要作用是防止死锁的产生* param3:时间单位,此处为秒*/if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {System.out.println(Thread.currentThread().getName() + ":线程1第一次获取锁成功");//实现可重入特性if (lock.tryLock(3, 5, TimeUnit.SECONDS)) {System.out.println(Thread.currentThread().getName() + ":线程1第二次获取锁成功");}//此处睡眠10秒演示线程2抢锁超时失败Thread.sleep(10000);} else {System.out.println(Thread.currentThread().getName() + ":线程1获取锁失败");}} catch (InterruptedException e) {System.out.println(Thread.currentThread().getName() + ":线程1获取锁异常");} finally {lock.unlock();}}}).start();new Thread(new Runnable() {@Overridepublic void run() {RLock lock = null;try {//此处睡眠1秒为防止线程2抢先执行抢锁Thread.sleep(1000);lock = redissonClient.getLock("lock");if (lock.tryLock(3, 5, TimeUnit.SECONDS)) {System.out.println(Thread.currentThread().getName() + ":线程2获取锁成功");} else {System.out.println(Thread.currentThread().getName() + ":线程2获取锁失败");}} catch (InterruptedException e) {System.out.println(Thread.currentThread().getName() + ":线程2获取锁异常");} finally {lock.unlock();}}}).start();}
}

4. 输出结果
线程1两次获取锁成功,实现了锁的可重入特性,线程2由于线程1抢到锁后睡眠10秒钟,导致线程2抢锁超时失败。

此处将线程1持锁时间改为5秒,线程2抢锁时间改为8秒,再次运行。
线程1修改代码:

//修改线程1持锁时间为5秒
if (lock.tryLock(3, 5, TimeUnit.SECONDS)) {System.out.println(Thread.currentThread().getName() + ":线程1第一次获取锁成功");//实现可重入特性if (lock.tryLock(3, 5, TimeUnit.SECONDS)) {System.out.println(Thread.currentThread().getName() + ":线程1第二次获取锁成功");}//此处睡眠10秒演示线程2抢锁超时失败Thread.sleep(10000);} else {System.out.println(Thread.currentThread().getName() + ":线程1获取锁失败");}

线程1修改代码:

if (lock.tryLock(8, 5, TimeUnit.SECONDS)) {System.out.println(Thread.currentThread().getName() + ":线程2获取锁成功");} else {System.out.println(Thread.currentThread().getName() + ":线程2获取锁失败");}

输出结果:
此处即使线程1抢到锁后睡眠了10秒,但在超过持锁时间后还时会自动释放锁,所以线程2可以抢锁成功,此方法可以预防多线程情况下某个服务器的线程获取到锁后宕机造成死锁现象。


三、Zookeeper实现分布式锁

此处使用Curator框架来实现。

1. 测试

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.RetryOneTime;
import org.junit.Test;import java.util.concurrent.TimeUnit;public class ZookeeperDistributedLock {private static final String CONNECTION = "127.0.0.1";private static final String LOCK_PATH = "/lockPath";@Testpublic void zkLock() throws InterruptedException {/*** 创建zookeeper客户端* sessionTimeoutMs代表会话时间,此处为默认时间60秒* connectionTimeoutMs代表连接超时时间,此处为默认时间15秒* retryPolicy代表重试策略,此处间隔一秒后重试一次*/CuratorFramework client = CuratorFrameworkFactory.builder().connectString(CONNECTION).sessionTimeoutMs(60000).connectionTimeoutMs(15000).retryPolicy(new RetryOneTime(1000)).build();//启动客户端client.start();//获取互斥锁InterProcessMutex mutex = new InterProcessMutex(client, LOCK_PATH);new Thread(() -> {try {//获取锁的最大时长为3秒if (mutex.acquire(3, TimeUnit.SECONDS)) {System.out.println(Thread.currentThread().getName() + ":线程1第一次获取锁成功");           //实现锁的可重入特性if (mutex.acquire(3, TimeUnit.SECONDS)) {System.out.println(Thread.currentThread().getName() + ":线程1第二次获取锁成功");
//                        Thread.sleep(10000);mutex.release();System.out.println(Thread.currentThread().getName() + ":线程1第一次释放锁成功");} else {System.out.println(Thread.currentThread().getName() + ":线程1第二次获取锁失败");}} else {System.out.println(Thread.currentThread().getName() + ":线程1第一次获取锁失败");}} catch (Exception e) {System.out.println(Thread.currentThread().getName() + ":线程1操作锁异常");e.printStackTrace();} finally {try {mutex.release();System.out.println(Thread.currentThread().getName() + ":线程1第二次释放锁成功");} catch (Exception e) {System.out.println(Thread.currentThread().getName() + ":线程1释放锁异常");e.printStackTrace();}}}).start();new Thread(() -> {try {//防止线程2抢先执行Thread.sleep(1000);if (mutex.acquire(5, TimeUnit.SECONDS)) {System.out.println(Thread.currentThread().getName() + ":线程2获取锁成功");} else {System.out.println(Thread.currentThread().getName() + ":线程2获取锁失败");}} catch (Exception e) {System.out.println(Thread.currentThread().getName() + ":线程2获取锁异常");e.printStackTrace();} finally {try {System.out.println(Thread.currentThread().getName() + ":线程2释放锁");mutex.release();} catch (Exception e) {System.out.println(Thread.currentThread().getName() + ":线程2释放锁异常");e.printStackTrace();}}}).start();//防止主线程执行完毕关闭程序Thread.sleep(Integer.MAX_VALUE);}
}

2. 输出结果
线程1可以两次获取锁,实现了锁的可重入性
此时放开线程1第二次获取锁时睡眠10秒的代码,再次运行结果:
线程2获取锁时,由于线程1没有释放锁,导致线程2抢锁失败,释放锁异常是因为没有抢到锁。

四、总结

在目前分布式锁实现方案中,比较成熟、主流的方案有两种:

(1)基于Redis的分布式锁

(2)基于ZooKeeper的分布式锁

两种锁,分别适用的场景为:

(1)基于ZooKeeper的分布式锁,适用于对数据一致性要求较高,而并发量不是太大的场景;

(2)基于Redis的分布式锁,适用于并发量很大、性能要求很高的、而对数据一致性的要求较低,允许实现最终一致性的场景。

欢迎大家共同探讨,如有错误,还望指正!

参考资料
[1]什么是分布式锁?实现分布式锁的三种方式
[2]Zookeeper 分布式锁 - 图解 - 秒懂

Java实现分布式锁的三种方式相关推荐

  1. memcached 分布式锁 java_分布式锁的三种实现方式

    分布式锁的三种实现方式 一.zookeeper 1.实现原理: 基于zookeeper瞬时有序节点实现的分布式锁,其主要逻辑如下(该图来自于IBM网站).大致思想即为:每个客户端对某个功能加锁时,在z ...

  2. 分布式锁以及三种加锁方式

    在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务.分布式锁等.那具体什么是分布式锁,分布式锁应用在哪些业务场景.如何来实现分布式锁呢? 一 为什么要使用分布式锁 我 ...

  3. 【分布式锁】三种分布式锁的实现【原创】

    分布式锁 0x00 概述 0x02 实现方式 0x03 分布式锁:基于数据库 1. 实现思想 A. 悲观锁(排他锁) B. 乐观锁 2. 优缺点 0x04 分布式锁:基于Zookeeper 1. 实现 ...

  4. Java中实现接口的三种方式您造吗?

    本文介绍了Java中实现接口的三种方式:常规实现方式.匿名内部类和 Lambda表达式实现方式.希望已经了解的同学可以重新温习一下,不了解的同学则从中受益! Java中接口最常规的实现方式 同学们都会 ...

  5. java中实现多线程的三种方式

    java中实现多线程的三种方式 1.实现多线程的方法: 在java中实现多线程的两途径:继承Thread类,实现Runable接口(Callable) 2.继承Thread类实现多线程: ​ 继承类T ...

  6. java中Map遍历的三种方式

    Java中Map遍历的三种方式 前言 一:在for循环中使用entries实现Map的遍历: 二:在for循环中遍历key或者values,一般适用于只需要map中的key或者value时使用,在性能 ...

  7. Java 创建一个线程的三种方式

    Java 创建一个线程的三种方式 更多内容,点击了解: https://how2j.cn/k/thread/thread-start/353.html 创建多线程有3种方式,分别是继承线程类,实现Ru ...

  8. 分布式锁的三种实现方式_基于 redis 的分布式锁实现

    云龙 资深运维开发工程师,负责游戏系统配置管理平台的设计和开发,目前专注于新 CMDB 系统的开发,平时也关注运维自动化,devops,python 开发等技术. 背景 CMDB 系统里面的机器数据会 ...

  9. java实现线程安全的三种方式

    前言 一个程序在运行起来的时候会转换成进程,通常含有多个线程.通常情况下,一个进程中的比较耗时的操作(如长循环.文件上传下载.网络资源获取等),往往会采用多线程来解决. 比如现实生活中,银行取钱问题. ...

最新文章

  1. 大数据项目开发案例_大数据分析技术——项目案例2(房价数据分析上)
  2. HttpClient超时重复提交问题
  3. 通俗理解checked Exception和unchecked Exception
  4. 非主流图片编辑软件_快火拼多多直播发言软件怎么下载
  5. java cursor 删除_使用CursorRecyclerAdatper在RecyclerView中删除和撤消
  6. STM32启动文件代码解析
  7. 华为Mate 30系列或下血本采用双主摄方案:CMOS尺寸破纪录
  8. python串口连续数据_Python代码从串口连续接收可变数据
  9. IDEA中Ctrl+Shift+f快捷键无效的解决方式
  10. 软工文档-操作手册和用户手册的区别
  11. App Store打了这么多年,ASO优化还剩什么?
  12. 常见汉字与不常见汉字
  13. 无线渗透笔记(二)-《使用Aircrack-ng破解握手包》
  14. 解决各个浏览器文件下载中文乱码问题
  15. 在nvidia的JETSON XACIER NX上面跑通yolov3
  16. 【报错总结】无法连接Hive的MetaStore数据库
  17. z390能装2012服务器系统,b360/h370/z390主板可以安装win7系统(完美支持)
  18. Transform.RotateAround 围绕旋转
  19. jeesite使用说明
  20. 六级核心词汇(不熟悉部分)

热门文章

  1. Pytorch 中的 unfold 和 fold
  2. 转帖:长时间坐着工作腰疼的解决办法
  3. 联诚发4000m²巨屏助力深圳大鹏音乐嘉年华震撼开唱
  4. 什么蓝牙耳机好用又不贵?五款四百元内的耳机推荐
  5. GO语言————5.4 for 结构
  6. 如何使3D打印零件装配在一起
  7. 珠海先达MES系统六大功能解决电子组装行业可视化问题
  8. 哈希分布与一致性哈希算法—在swift中看到这个有意思的算法
  9. 搬运:您的安全设置不允许将此应用程序安装到您的计算机上
  10. 计算机专业转专业申请书范文,常用的转专业申请书范文(2017最新版)