如何在Java项目中实现分布式锁

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
云原生网关 MSE Higress,422元/月
简介: 如何在Java项目中实现分布式锁

如何在Java项目中实现分布式锁

在分布式系统中,多个节点同时访问共享资源时,为了保证数据的一致性和避免冲突,通常需要使用分布式锁。分布式锁能够确保在任何时候,只有一个节点能够对共享资源进行操作,从而避免数据错乱和并发问题。本文将深入探讨在Java项目中实现分布式锁的几种常见方法及其实现细节。

基于数据库实现分布式锁

一种简单而有效的分布式锁实现方式是基于数据库。通过在数据库中创建一张表,利用数据库的事务和唯一索引特性来确保同一时间只有一个客户端可以获取锁。

package cn.juwatech.lock;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class DatabaseLock {
    private static final String JDBC_URL = "jdbc:mysql://localhost:3306/test";
    private static final String USER = "username";
    private static final String PASSWORD = "password";
    private static final String LOCK_SQL = "INSERT INTO distributed_lock (lock_name) VALUES (?)";
    private static final String UNLOCK_SQL = "DELETE FROM distributed_lock WHERE lock_name = ?";
    public boolean acquireLock(String lockName) {
        try (Connection conn = DriverManager.getConnection(JDBC_URL, USER, PASSWORD);
             PreparedStatement stmt = conn.prepareStatement(LOCK_SQL)) {
            stmt.setString(1, lockName);
            int rowsAffected = stmt.executeUpdate();
            return rowsAffected > 0;
        } catch (SQLException e) {
            e.printStackTrace();
            return false;
        }
    }
    public boolean releaseLock(String lockName) {
        try (Connection conn = DriverManager.getConnection(JDBC_URL, USER, PASSWORD);
             PreparedStatement stmt = conn.prepareStatement(UNLOCK_SQL)) {
            stmt.setString(1, lockName);
            int rowsAffected = stmt.executeUpdate();
            return rowsAffected > 0;
        } catch (SQLException e) {
            e.printStackTrace();
            return false;
        }
    }
}

在上述示例中,我们通过执行INSERT语句来尝试获取锁,并通过DELETE语句释放锁。需要注意的是,基于数据库的锁虽然简单易懂,但在高并发场景下性能可能成为瓶颈,因此需要谨慎选择数据库和优化SQL语句。

基于Redis实现分布式锁

Redis作为一个高性能的内存数据存储系统,也是实现分布式锁的常用选择。Redis提供了SETNX(SET if Not eXists)命令,可以原子性地设置键值对,因此可以用来实现分布式锁。

package cn.juwatech.lock;
import redis.clients.jedis.Jedis;
public class RedisLock {
    private static final String REDIS_HOST = "localhost";
    private static final int REDIS_PORT = 6379;
    private static final String LOCK_KEY = "distributed_lock";
    private static final int LOCK_EXPIRE_TIME = 30000; // 锁的过期时间,单位毫秒
    private Jedis jedis;
    public RedisLock() {
        this.jedis = new Jedis(REDIS_HOST, REDIS_PORT);
    }
    public boolean acquireLock() {
        String result = jedis.set(LOCK_KEY, "locked", "NX", "PX", LOCK_EXPIRE_TIME);
        return "OK".equals(result);
    }
    public void releaseLock() {
        jedis.del(LOCK_KEY);
    }
}

在以上示例中,我们使用了Redis的set命令来尝试获取锁,并设置了过期时间,这样即使锁未被释放,也能在一定时间后自动释放。

基于ZooKeeper实现分布式锁

ZooKeeper作为一个分布式协调服务,也可以用来实现分布式锁。通过创建有序临时节点,利用其顺序特性来实现锁的竞争和释放。

package cn.juwatech.lock;
import org.apache.zookeeper.*;
public class ZooKeeperLock implements Watcher {
    private static final String ZOOKEEPER_HOST = "localhost:2181";
    private static final int SESSION_TIMEOUT = 3000;
    private ZooKeeper zooKeeper;
    private String lockPath;
    public ZooKeeperLock() throws Exception {
        this.zooKeeper = new ZooKeeper(ZOOKEEPER_HOST, SESSION_TIMEOUT, this);
    }
    public boolean acquireLock() throws KeeperException, InterruptedException {
        // 创建临时有序节点
        lockPath = zooKeeper.create("/locks/node_", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,
                CreateMode.EPHEMERAL_SEQUENTIAL);
        // 检查是否是最小节点
        String[] nodes = zooKeeper.getChildren("/locks", false).toArray(new String[0]);
        for (String node : nodes) {
            if (lockPath.endsWith(node)) {
                return true;
            }
        }
        return false;
    }
    public void releaseLock() throws InterruptedException, KeeperException {
        zooKeeper.delete(lockPath, -1);
    }
    @Override
    public void process(WatchedEvent event) {
        // 实现Watcher接口的方法
    }
}

在上述示例中,我们使用ZooKeeper的临时有序节点来实现锁,通过创建节点的顺序来判断是否获得锁。

分布式锁的选择与总结

不同的分布式锁实现方案各有优缺点,应根据具体的业务场景和性能需求选择合适的实现方式。基于数据库的锁简单易懂,适合对一致性要求不高的场景;基于Redis和ZooKeeper的实现方式更适合对性能和一致性有高要求的场景。

通过本文的介绍,希望读者能够深入理解分布式锁的工作原理及其在Java项目中的实现方式,并能在实际应用中进行灵活应用和调整。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
3天前
|
Java Android开发
Eclipse 创建 Java 项目
Eclipse 创建 Java 项目
17 4
|
8天前
|
SQL Java 数据库连接
从理论到实践:Hibernate与JPA在Java项目中的实际应用
本文介绍了Java持久层框架Hibernate和JPA的基本概念及其在具体项目中的应用。通过一个在线书店系统的实例,展示了如何使用@Entity注解定义实体类、通过Spring Data JPA定义仓库接口、在服务层调用方法进行数据库操作,以及使用JPQL编写自定义查询和管理事务。这些技术不仅简化了数据库操作,还显著提升了开发效率。
20 3
|
11天前
|
前端开发 Java 数据库
如何实现一个项目,小白做项目-java
本教程涵盖了从数据库到AJAX的多个知识点,并详细介绍了项目实现过程,包括静态页面分析、数据库创建、项目结构搭建、JSP转换及各层代码编写。最后,通过通用分页和优化Servlet来提升代码质量。
28 1
|
13天前
|
存储 NoSQL Java
Java调度任务如何使用分布式锁保证相同任务在一个周期里只执行一次?
【10月更文挑战第29天】Java调度任务如何使用分布式锁保证相同任务在一个周期里只执行一次?
36 1
|
1月前
|
JavaScript 前端开发 Java
解决跨域问题大集合:vue-cli项目 和 java/springboot(6种方式) 两端解决(完美解决)
这篇文章详细介绍了如何在前端Vue项目和后端Spring Boot项目中通过多种方式解决跨域问题。
334 1
解决跨域问题大集合:vue-cli项目 和 java/springboot(6种方式) 两端解决(完美解决)
|
18天前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。
|
1月前
|
Java Apache Maven
Java/Spring项目的包开头为什么是com?
本文介绍了 Maven 项目的初始结构,并详细解释了 Java 包命名惯例中的域名反转规则。通过域名反转(如 `com.example`),可以确保包名的唯一性,避免命名冲突,提高代码的可读性和逻辑分层。文章还讨论了域名反转的好处,包括避免命名冲突、全球唯一性、提高代码可读性和逻辑分层。最后,作者提出了一个关于包名的问题,引发读者思考。
Java/Spring项目的包开头为什么是com?
|
1月前
|
运维 Java Maven
Dockerfile实践java项目
通过上述实践,我们可以看到,Dockerfile在Java项目中扮演着至关重要的角色,它不仅简化了部署流程,提高了环境一致性,还通过多阶段构建、环境变量配置、日志管理、健康检查等高级特性,进一步增强了应用的可维护性和可扩展性。掌握这些实践,将极大地提升开发和运维团队的工作效率。
46 1
|
1月前
|
算法 Java Linux
java制作海报五:java 后端整合 echarts 画出 折线图,项目放在linux上,echarts图上不显示中文,显示方框口口口
这篇文章介绍了如何在Java后端整合ECharts库来绘制折线图,并讨论了在Linux环境下ECharts图表中文显示问题。
39 1
|
1月前
|
运维 Java Maven
Dockerfile实践java项目
通过上述实践,我们可以看到,Dockerfile在Java项目中扮演着至关重要的角色,它不仅简化了部署流程,提高了环境一致性,还通过多阶段构建、环境变量配置、日志管理、健康检查等高级特性,进一步增强了应用的可维护性和可扩展性。掌握这些实践,将极大地提升开发和运维团队的工作效率。
21 1