使用JDK原生的ReentrantLock读写锁解决写操作并发问题

本文通过实例展示了如何使用JDK的ReentrantReadWriteLock解决并发编程中的写操作并发问题。在未加锁的情况下,写操作与其他写操作或读操作并发执行,导致数据不一致。通过为资源类的`set`和`get`方法添加读写锁,确保了写操作的原子性,实现了写与写互斥,写与读互斥,读与读可并发的效果,从而保证了数据的一致性和并发安全性。

使用JDK原生的ReentrantLock读写锁解决写操作并发问题

ReentrantLockDemo

测试类

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        for (int i = 1; i <= 5; i++) {
            final int it = i;
            new Thread(() ->{
                myCache.set(it+"key",it+"value");
            },String.valueOf(i)).start();
            new Thread(() ->{
                myCache.get(it+"key");
            },String.valueOf(i)).start();
        }
    }
}

资源类(加锁之前)

class MyCache1 {
    Map<String, Object> map = new HashMap<>();
    ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

    public void set(String key, Object value) {
        System.out.println(Thread.currentThread().getName() + "正在写入" + key);
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + "写入完成");
    }

    public void get(String key) {
        System.out.println(Thread.currentThread().getName() + "正在读取" + key);
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Object result = map.get(key);
        System.out.println(Thread.currentThread().getName() + "读取结果:" + result);
    }
}

测试结果:

在这里插入图片描述

写操作应该具有原子性,写操作不能与写操作并发,也不能与读并发。但是测试结果显示,写操作与其他的写操作或者读操作并发执行了。那么我们对资源类进行改造:

资源类(加锁之后)

class MyCache {
    Map<String, Object> map = new HashMap<>();
    ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

    public void set(String key, Object value) {
        try {
            rwLock.writeLock().lock();
            System.out.println(Thread.currentThread().getName() + "正在写入" + key);
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put(key,value);
            System.out.println(Thread.currentThread().getName() + "写入完成");
        } finally {
            rwLock.writeLock().unlock();
        }
    }

    public void get(String key) {
        try {
            rwLock.readLock().lock();
            System.out.println(Thread.currentThread().getName() + "正在读取" + key);
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Object result = map.get(key);
            System.out.println(Thread.currentThread().getName() + "读取结果:" +result);
        } finally {
            rwLock.readLock().unlock();
        }
    }
}

测试结果:

在这里插入图片描述

测试结果显示完美,写与写操作不能并发,写与读不能并发,读与读可以并发。

测试结果对比:

在这里插入图片描述

测试结果显示完美,写与写操作不能并发,写与读不能并发,读与读可以并发。

源码及其他并发demo见github

<think>我们正在讨论synchronized关键字在微服务架构中的适用性。根据用户问题,需要分析synchronized在分布式环境下的有效性。首先,synchronized是Java中用于线程同步的关键字,它通过对象监视器锁(Monitor)来实现同步,确保同一时刻只有一个线程可以执行特定的代码段。然而,在微服务架构中,服务通常部署在多个节点(多台机器)上,每个节点有自己的JVM实例。synchronized只能保证单个JVM内的线程同步,无法跨JVM同步。因此,在单个服务实例内部,synchronized可以用于解决多线程并发问题,例如保护实例内的共享资源(如缓存、计数器等)。但是,对于跨多个服务实例的共享资源(如分布式缓存、数据库等),synchronized就无能为力了,因为每个实例的锁都是独立的。此时,我们需要分布式锁机制,例如使用Redis、ZooKeeper或数据库等中间件来实现跨JVM的锁。引用中提到的内容: [^1]:展示了synchronized在单JVM内实现心跳计数器的例子,说明它在单实例内是有效的。[^2][^3][^4][^5]:都是关于synchronized在单JVM内多线程同步的机制和用法。所以,我们可以这样回答:在微服务架构中,如果并发控制仅限于单个服务实例内部(例如实例内的线程安全),则synchronized是适用的。但如果需要跨多个服务实例的全局并发控制,则synchronized不适用,需要使用分布式锁。同时,我们还可以提到,在微服务架构中,由于每个服务实例是无状态的,我们通常将状态外置(如数据库、Redis等),这样在服务实例内部使用synchronized的场景也会减少。最后,生成相关问题。</think>### synchronized关键字在微服务架构中的适用性分析 synchronized关键字**可以用于解决单个服务实例内部的并发问题**,但**无法直接解决跨服务实例的分布式并发问题**。具体分析如下: #### ✅ 适用场景(单实例内) 1. **单实例线程安全** 当多个线程访问同一JVM内的共享资源时,synchronized能有效保证原子性和可见性: ```java public class ServiceRegistry { private final Map<String, ServiceInstance> instances = new HashMap<>(); // 同步方法保证单实例注册表线程安全 public synchronized void register(ServiceInstance instance) { instances.put(instance.getId(), instance); } } ``` 引用[^1]中的心跳计数器案例正是此类场景的典型应用。 2. **无状态服务并发控制** 在无状态服务中处理瞬时状态时(如请求计数器): ```java public class RequestCounter { private int count = 0; public synchronized void increment() { count++; // 保证原子操作 } } ``` 3. **资源池管理** 管理单实例内的连接池、线程池等资源: ```java public class ConnectionPool { private final Queue<Connection> pool = new LinkedList<>(); public synchronized Connection borrow() { return pool.poll(); } } ``` #### ❌ 局限性(分布式场景) 1. **跨JVM失效** synchronized的锁机制基于JVM级监视器(Monitor),无法跨服务实例生效。引用[^4]明确指出:静态方法同步`SynchronizedMethods.class`仅作用于当前JVM。 2. **分布式竞争场景** 典型需分布式锁的场景: - 分布式ID生成 - 跨服务共享资源(如库存扣减) - 集群任务调度 ```mermaid graph LR A[服务实例A] -->|尝试扣减库存| D[(数据库)] B[服务实例B] -->|同时请求| D D -->|库存竞争| C[数据不一致风险] ``` 3. **性能瓶颈** 在引用[^5]中说明:synchronized的互斥特性可能导致: - 线程阻塞增加延迟 - 不适用于高频并发场景(如10K+ QPS) - 无法实现读分离优化 #### ⚡ 微服务架构建议方案 | 场景 | 推荐方案 | 特点 | |---------------------|------------------------|--------------------------| | 单实例线程安全 | synchronized | 实现简单,JDK原生支持[^4] | | 跨实例分布式锁 | Redis/ZooKeeper | 基于CP/AP模型 | | 高性能无锁控制 | CAS(AtomicInteger等) | 避免线程阻塞 | | 复杂事务 | 分布式事务框架(Seata) | 保证ACID | > 引用[^2]提到的`AQS`框架(如ReentrantLock)可作为synchronized的替代方案,提供更灵活的锁机制,但同样受限于单JVM。 ### 总结 synchronized**适用于**微服务中**单实例内部的并发控制**(如注册表管理、本地计数器),但**不适用于**需要**跨服务协调的分布式并发场景**。在分布式环境下需结合Redis分布式锁、CAS无锁机制或分布式事务框架实现全局一致性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值