在分布式场景下可以使用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 来实现同步,需要根据具体的业务场景选择合适的分布式锁方案。

相关推荐
数据智能老司机13 小时前
CockroachDB权威指南——CockroachDB SQL
数据库·分布式·架构
数据智能老司机14 小时前
CockroachDB权威指南——开始使用
数据库·分布式·架构
数据智能老司机14 小时前
CockroachDB权威指南——CockroachDB 架构
数据库·分布式·架构
IT成长日记15 小时前
【Kafka基础】Kafka工作原理解析
分布式·kafka
州周16 小时前
kafka副本同步时HW和LEO
分布式·kafka
爱的叹息18 小时前
主流数据库的存储引擎/存储机制的详细对比分析,涵盖关系型数据库、NoSQL数据库和分布式数据库
数据库·分布式·nosql
千层冷面19 小时前
RabbitMQ 发送者确认机制详解
分布式·rabbitmq·ruby
ChinaRainbowSea19 小时前
3. RabbitMQ 的(Hello World) 和 RabbitMQ 的(Work Queues)工作队列
java·分布式·后端·rabbitmq·ruby·java-rabbitmq
敖正炀19 小时前
基于RocketMQ的可靠消息最终一致性分布式事务解决方案
分布式
一條狗21 小时前
随笔 20250402 分布式 ID 生成器 Snowflake 里面的坑
分布式