Java单例JAX-RS服务并发管理方案可行性问询
你的当前实现是否可行?结论:不符合需求,我来拆解问题并给出解决方案
问题分析
1. synchronized(this)的核心问题
因为你的类标注了@Singleton,整个应用中只会存在一个NfvDeployer实例。你在POST方法里使用synchronized(this),意味着只要有一个POST请求在执行同步块,所有其他请求(包括GET)都会被阻塞——当POST持有实例锁时,即使GET方法没有加同步块,后续的GET请求如果需要访问共享数据,要么等待锁释放,要么在无锁保护下出现数据异常,完全违背了你“GET请求同时处理”的需求。
2. 共享集合的线程安全隐患
你使用的List<String>如果是普通的ArrayList(默认场景),它并非线程安全容器。当POST在修改集合(add(Node.ID))的同时,GET在遍历集合,极大概率会抛出ConcurrentModificationException,或者出现数据不一致的情况(比如遍历到一半集合被修改,拿到不完整/错误的数据)。
3. 静态成员的冗余问题
因为你的类是@Singleton,整个应用只有一个实例,用static修饰allocatedNodesOnHost等Map是完全没必要的,反而容易造成混淆(静态成员属于类而非实例),建议改成非静态成员。
解决方案:使用读写锁(ReentrantReadWriteLock)
要实现“GET请求并发处理,POST请求串行执行”的需求,最适配的工具就是读写锁。它的核心规则非常贴合你的场景:
- 读锁:允许多个线程同时持有,适合只读操作(比如你的GET方法)
- 写锁:同一时间只允许一个线程持有,且写锁与读锁互斥(写操作执行时不能有读操作,读操作执行时不能有写操作)
修改后的代码示例
@Singleton @Path("/") public class NfvDeployer { // 改用非静态成员,单例实例唯一,无需静态修饰 private final Map<String, List<String>> allocatedNodesOnHost = new HashMap<>(); private final Map<String, String> loadedHosts = new HashMap<>(); private final Map<String, String> loadedNodes = new HashMap<>(); // 初始化读写锁 private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); private final Lock readLock = rwLock.readLock(); private final Lock writeLock = rwLock.writeLock(); @POST @Path("nffgs/{id}/nodes") @Produces(MediaType.APPLICATION_XML) @Consumes(MediaType.APPLICATION_XML) public MyNode postNodeOnNFFG(MyNode node, @PathParam("id") String id) { // 写操作:获取排他写锁 writeLock.lock(); try { // 你的业务逻辑,比如获取对应的H值 String H = ...; // 自动创建不存在的集合,避免空指针 allocatedNodesOnHost.computeIfAbsent(H, k -> new CopyOnWriteArrayList<>()); allocatedNodesOnHost.get(H).add(node.ID); // 其他业务处理... return node; } finally { // 必须在finally中释放锁,防止异常导致锁泄漏 writeLock.unlock(); } } @GET @Path("hosts/{id}/nodes") @Produces(MediaType.APPLICATION_XML) public MyNodes getNodeFromNFFG(@PathParam("id") String id) { // 读操作:获取共享读锁 readLock.lock(); try { MyNodes result = new MyNodes(); List<String> nodeIds = allocatedNodesOnHost.getOrDefault(id, Collections.emptyList()); for(String s : nodeIds) { // 构建返回结果的逻辑... result.addNode(...); } return result; } finally { readLock.unlock(); } } }
额外优化说明
- 使用
CopyOnWriteArrayList作为Map的值类型:它本身是线程安全容器,适合读多写少的场景,即使在极端情况下也不会抛出并发修改异常。 - 用
computeIfAbsent简化集合初始化:避免手动检查集合是否存在,同时保证线程安全。 - 锁的释放放在
finally块:无论业务代码是否抛出异常,都能确保锁被正确释放,避免死锁风险。
总结
你的当前实现无法满足“GET并发、POST串行”的需求,synchronized(this)会导致所有请求串行化,且共享集合存在线程安全风险。改用ReentrantReadWriteLock可以完美匹配你的需求,同时保证数据一致性和线程安全。
内容的提问来源于stack exchange,提问作者helloimcarmine
相关产品推荐
相关产品推荐

