在分布式场景下可以使用synchronized加锁么?

首先说结论,在分布式系统中,单纯使用 Java 中的 synchronized 关键字是无法满足需求的,下面从 synchronized 的作用原理、在分布式场景下的局限性以及替代方案等方面详细分析。

一、synchronized 的作用原理

在 Java 中,synchronized 关键字用于实现线程同步,它可以保证在同一时刻,只有一个线程能够访问被 synchronized 修饰的代码块或方法。其本质是通过获取对象的监视器(monitor)来实现互斥访问,这是基于 JVM 层面的同步机制,作用范围仅限于单个 JVM 进程内。

以下是一个简单的 synchronized 使用示例:

java 复制代码
public class SynchronizedExample {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public static void main(String[] args) throws InterruptedException {
        SynchronizedExample example = new SynchronizedExample();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("Count: " + example.count);
    }
}

在这个示例中,increment 方法被 synchronized 修饰,确保了在同一时刻只有一个线程能够执行该方法,从而避免了多线程环境下的竞态条件。

二、在分布式场景下的局限性

分布式系统由多个独立的 JVM 进程组成,不同进程之间无法直接共享对象的监视器。synchronized 只能保证单个 JVM 内的线程同步,无法实现跨 JVM 进程的同步。因此,在分布式系统中,如果多个进程同时访问共享资源,使用 synchronized 无法保证资源的互斥访问,可能会导致数据不一致等问题。

三、分布式系统中的替代方案

为了实现分布式环境下的同步,可以使用以下几种常见的方案:

1.数据库锁

可以利用数据库的行级锁或表级锁来实现分布式锁。例如,在 MySQL 中,可以使用 SELECT ... FOR UPDATE 语句来获取行级锁。

sql 复制代码
-- 获取行级锁
SELECT * FROM distributed_lock_table WHERE lock_name = 'resource_lock' FOR UPDATE;

这种方式的优点是实现简单,不需要额外的组件;缺点是性能较差,对数据库的依赖较大。

2.Redis 分布式锁------SETNX

Redis 是一个高性能的键值存储系统,可以利用 Redis 的原子操作来实现分布式锁。常见的实现方式是使用 SETNX(SET if Not eXists)命令。

java 复制代码
import redis.clients.jedis.Jedis;

public class RedisDistributedLock {
    private static final String LOCK_KEY = "distributed_lock";
    private static final String LOCK_VALUE = "lock_value";
    private static final int EXPIRE_TIME = 1000; // 锁的过期时间,单位:毫秒

    public static boolean acquireLock(Jedis jedis) {
        String result = jedis.set(LOCK_KEY, LOCK_VALUE, "NX", "PX", EXPIRE_TIME);
        return "OK".equals(result);
    }

    public static void releaseLock(Jedis jedis) {
        jedis.del(LOCK_KEY);
    }

    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);
        if (acquireLock(jedis)) {
            try {
                // 执行临界区代码
                System.out.println("获取到锁,执行临界区代码");
            } finally {
                releaseLock(jedis);
            }
        } else {
            System.out.println("未获取到锁");
        }
        jedis.close();
    }
}

这种方式的优点是性能高,实现相对简单;缺点是需要额外的 Redis 服务,并且需要处理锁的过期时间和异常情况。

3.Redis 分布式锁------TryLock

事实上,Redis 实现 TryLock 也要依靠 SET 命令的原子性,通过设置特定的键值对以及过期时间来模拟锁的获取和释放。当执行 SET 命令时,如果键不存在则设置成功,相当于获取到锁;若键已存在则设置失败,代表锁已被其他客户端持有。

以下是使用 Jedis实现 tryLock 功能的示例代码:

java 复制代码
import redis.clients.jedis.Jedis;

public class RedisTryLockExample {
    private static final String LOCK_KEY = "distributed_lock";
    private static final String LOCK_VALUE = "lock_value";
    private static final int EXPIRE_TIME = 10000; // 锁的过期时间,单位:毫秒
    private Jedis jedis;

    public RedisTryLockExample() {
        this.jedis = new Jedis("localhost", 6379);
    }

    /**
     * 尝试获取锁
     * @return 如果获取到锁返回 true,否则返回 false
     */
    public boolean tryLock() {
        // 使用 SET 命令的 NX(Not eXists)和 PX(过期时间)选项
        String result = jedis.set(LOCK_KEY, LOCK_VALUE, "NX", "PX", EXPIRE_TIME);
        return "OK".equals(result);
    }

    /**
     * 释放锁
     */
    public void unlock() {
        jedis.del(LOCK_KEY);
    }

    public static void main(String[] args) {
        RedisTryLockExample lockExample = new RedisTryLockExample();
        if (lockExample.tryLock()) {
            try {
                // 模拟执行临界区代码
                System.out.println("获取到锁,执行临界区代码");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lockExample.unlock();
                System.out.println("释放锁");
            }
        } else {
            System.out.println("未获取到锁");
        }
        lockExample.jedis.close();
    }
}

比较SETNX和TryLock:

SETNX 的问题 :使用 SETNX 命令设置锁时,无法原子性地设置锁的过期时间。若在设置完锁之后,在设置过期时间之前,客户端发生异常崩溃,就会导致锁无法释放,造成死锁。**SETNX**只是单纯地判断键是否存在并进行设置,对于锁的过期时间等额外属性需要额外的命令来处理,在实现分布式锁时需要编写更多的代码来保证正确性,代码的可读性和可维护性较差。功能相对单一,仅能实现键的存在性判断和设置操作。

T**ryLock 实现的优势** :使用 SET 命令并结合 NXPX/EX 选项可以原子性地完成锁的设置和过期时间的设置,避免了上述死锁问题。从功能语义上来说,TryLock 更符合锁的使用习惯,调用者可以直观地理解其作用是尝试获取锁,并且能够方便地结合过期时间等参数进行使用,代码逻辑更加清晰。TryLock基于 SET 命令可以利用 Redis 提供的更多特性,例如可以设置不同的过期时间单位(PX 表示毫秒,EX 表示秒),还可以结合其他命令实现更复杂的分布式锁逻辑,如可重入锁等。

四、总结

综上所述,在分布式系统中不能直接使用 synchronized 来实现同步,需要根据具体的业务场景选择合适的分布式锁方案。

相关推荐
Coder_Boy_2 小时前
基于Spring AI的分布式在线考试系统-事件处理架构实现方案
人工智能·spring boot·分布式·spring
袁煦丞 cpolar内网穿透实验室3 小时前
远程调试内网 Kafka 不再求运维!cpolar 内网穿透实验室第 791 个成功挑战
运维·分布式·kafka·远程工作·内网穿透·cpolar
人间打气筒(Ada)3 小时前
GlusterFS实现KVM高可用及热迁移
分布式·虚拟化·kvm·高可用·glusterfs·热迁移
xu_yule3 小时前
Redis存储(15)Redis的应用_分布式锁_Lua脚本/Redlock算法
数据库·redis·分布式
難釋懷8 小时前
分布式锁的原子性问题
分布式
ai_xiaogui9 小时前
【开源前瞻】从“咸鱼”到“超级个体”:谈谈 Panelai 分布式子服务器管理系统的设计架构与 UI 演进
服务器·分布式·架构·分布式架构·panelai·开源面板·ai工具开发
凯子坚持 c9 小时前
如何基于 CANN 原生能力,构建一个支持 QoS 感知的 LLM 推理调度器
分布式
飞升不如收破烂~9 小时前
Redis 分布式锁+接口幂等性使用+当下流行的限流方案「落地实操」+用户连续点击两下按钮的解决方案自用总结
数据库·redis·分布式
无心水9 小时前
分布式定时任务与SELECT FOR UPDATE:从致命陷阱到优雅解决方案(实战案例+架构演进)
服务器·人工智能·分布式·后端·spring·架构·wpf