From 02e1514012315b87d0693201898a76d6ab66d6b4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=91=E6=B8=85?=
<33415199+lightClouds917@users.noreply.github.com>
Date: Fri, 17 Jan 2025 16:06:46 +0800
Subject: [PATCH] feature:[loom] replace the usages of synchronized with
ReentrantLock (#7073)
---
changes/en-us/2.x.md | 2 +
changes/zh-cn/2.x.md | 2 +
.../seata/common/lock/ResourceLock.java | 60 ++++++
.../seata/common/util/UUIDGenerator.java | 5 +-
.../seata/common/lock/ResourceLockTest.java | 147 +++++++++++++++
.../eureka/EurekaRegistryServiceImpl.java | 7 +-
.../tx/api/fence/hook/TccHookManager.java | 5 +-
.../parser/DefaultRemotingParser.java | 5 +-
.../integration/tx/api/util/ProxyUtil.java | 4 +-
.../seata/rm/datasource/util/JdbcUtils.java | 4 +-
.../rm/datasource/xa/ConnectionProxyXA.java | 178 ++++++++++--------
.../rm/datasource/xa/ResourceManagerXA.java | 6 +-
.../rocketmq/SeataMQProducerFactory.java | 5 +-
.../seata/server/session/GlobalSession.java | 6 +-
14 files changed, 345 insertions(+), 91 deletions(-)
create mode 100644 common/src/main/java/org/apache/seata/common/lock/ResourceLock.java
create mode 100644 common/src/test/java/org/apache/seata/common/lock/ResourceLockTest.java
diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md
index 5940b569f7d..419f3f63371 100644
--- a/changes/en-us/2.x.md
+++ b/changes/en-us/2.x.md
@@ -4,6 +4,7 @@ Add changes here for all PR submitted to the 2.x branch.
### feature:
+- [[#7073](https://github.com/apache/incubator-seata/pull/7073)] support virtual thread,replace the usages of synchronized with ReentrantLock
- [[#7037](https://github.com/apache/incubator-seata/pull/7037)] support fury undolog parser
- [[#7069](https://github.com/apache/incubator-seata/pull/7069)] Raft cluster mode supports address translation
- [[#7038](https://github.com/apache/incubator-seata/pull/7038)] support fury serializer
@@ -43,6 +44,7 @@ Thanks to these contributors for their code commits. Please report an unintended
- [slievrly](https://github.com/slievrly)
- [lyl2008dsg](https://github.com/lyl2008dsg)
- [remind](https://github.com/remind)
+- [lightClouds917](https://github.com/lightClouds917)
- [GoodBoyCoder](https://github.com/GoodBoyCoder)
- [PeppaO](https://github.com/PeppaO)
- [funky-eyes](https://github.com/funky-eyes)
diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md
index 13e4e531831..2f7d72c5ef5 100644
--- a/changes/zh-cn/2.x.md
+++ b/changes/zh-cn/2.x.md
@@ -4,6 +4,7 @@
### feature:
+- [[#7073](https://github.com/apache/incubator-seata/pull/7073)] 支持虚拟线程,用ReentrantLock替换synchronized的用法
- [[#7037](https://github.com/apache/incubator-seata/pull/7037)] 支持UndoLog的fury序列化方式
- [[#7069](https://github.com/apache/incubator-seata/pull/7069)] Raft集群模式支持地址转换
- [[#7038](https://github.com/apache/incubator-seata/pull/7038)] 支持Fury序列化器
@@ -42,6 +43,7 @@
- [slievrly](https://github.com/slievrly)
- [lyl2008dsg](https://github.com/lyl2008dsg)
- [remind](https://github.com/remind)
+- [lightClouds917](https://github.com/lightClouds917)
- [GoodBoyCoder](https://github.com/GoodBoyCoder)
- [PeppaO](https://github.com/PeppaO)
- [funky-eyes](https://github.com/funky-eyes)
diff --git a/common/src/main/java/org/apache/seata/common/lock/ResourceLock.java b/common/src/main/java/org/apache/seata/common/lock/ResourceLock.java
new file mode 100644
index 00000000000..22e815e5784
--- /dev/null
+++ b/common/src/main/java/org/apache/seata/common/lock/ResourceLock.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.seata.common.lock;
+
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * The ResourceLock extends ReentrantLock and implements AutoCloseable,
+ * allowing it to be used in try-with-resources blocks without needing
+ * to unlock in a finally block.
+ *
+ *
Example
+ *
+ * {@code
+ * private final ResourceLock resourceLock = new ResourceLock();
+ * try (ResourceLock lock = resourceLock.obtain()) {
+ * // do something while holding the resource lock
+ * }
+ * }
+ *
This is typically used in try-with-resources blocks to automatically
+ * unlock the resource lock when the block is exited, regardless of whether
+ * an exception is thrown or not.
+ */
+ @Override
+ public void close() {
+ this.unlock();
+ }
+}
diff --git a/common/src/main/java/org/apache/seata/common/util/UUIDGenerator.java b/common/src/main/java/org/apache/seata/common/util/UUIDGenerator.java
index 542de3ed1eb..7b8ea727599 100644
--- a/common/src/main/java/org/apache/seata/common/util/UUIDGenerator.java
+++ b/common/src/main/java/org/apache/seata/common/util/UUIDGenerator.java
@@ -16,12 +16,15 @@
*/
package org.apache.seata.common.util;
+import org.apache.seata.common.lock.ResourceLock;
+
/**
* The type Uuid generator.
*/
public class UUIDGenerator {
private static volatile IdWorker idWorker;
+ private final static ResourceLock RESOURCE_LOCK = new ResourceLock();
/**
* generate UUID using snowflake algorithm
@@ -30,7 +33,7 @@ public class UUIDGenerator {
*/
public static long generateUUID() {
if (idWorker == null) {
- synchronized (UUIDGenerator.class) {
+ try (ResourceLock ignored = RESOURCE_LOCK.obtain()) {
if (idWorker == null) {
init(null);
}
diff --git a/common/src/test/java/org/apache/seata/common/lock/ResourceLockTest.java b/common/src/test/java/org/apache/seata/common/lock/ResourceLockTest.java
new file mode 100644
index 00000000000..0ed2e7e2b7d
--- /dev/null
+++ b/common/src/test/java/org/apache/seata/common/lock/ResourceLockTest.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.seata.common.lock;
+
+import org.apache.seata.common.util.CollectionUtils;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@ExtendWith(MockitoExtension.class)
+public class ResourceLockTest {
+
+ @Test
+ public void testObtainAndClose() {
+ ResourceLock resourceLock = new ResourceLock();
+
+ // Test obtaining the lock
+ try (ResourceLock lock = resourceLock.obtain()) {
+ assertTrue(resourceLock.isHeldByCurrentThread(), "Lock should be held by current thread");
+ }
+
+ // After try-with-resources, lock should be released
+ assertFalse(resourceLock.isHeldByCurrentThread(), "Lock should be released after try-with-resources");
+ }
+
+ @Test
+ public void testMultipleObtainAndClose() {
+ ResourceLock resourceLock = new ResourceLock();
+
+ // First obtain and release
+ try (ResourceLock lock = resourceLock.obtain()) {
+ assertTrue(resourceLock.isHeldByCurrentThread(), "Lock should be held by current thread");
+ }
+ assertFalse(resourceLock.isHeldByCurrentThread(), "Lock should be released after first try-with-resources");
+
+ // Second obtain and release
+ try (ResourceLock lock = resourceLock.obtain()) {
+ assertTrue(resourceLock.isHeldByCurrentThread(), "Lock should be held by current thread");
+ }
+ assertFalse(resourceLock.isHeldByCurrentThread(), "Lock should be released after second try-with-resources");
+ }
+
+ @Test
+ public void testResourceLockAutoRemovalFromMap() {
+ ConcurrentHashMap lockMap = new ConcurrentHashMap<>();
+ String key = "testKey";
+ // Use try-with-resources to obtain and release the lock
+ try (ResourceLock ignored = CollectionUtils.computeIfAbsent(lockMap, key, k -> new ResourceLock()).obtain()) {
+ // Do something while holding the lock
+ assertTrue(lockMap.containsKey(key));
+ assertTrue(lockMap.get(key).isHeldByCurrentThread());
+ } finally {
+ assertFalse(lockMap.get(key).isHeldByCurrentThread());
+ assertTrue(lockMap.containsKey(key));
+ // Remove the lock from the map
+ lockMap.remove(key);
+ assertFalse(lockMap.containsKey(key));
+ }
+ // Ensure the lock is removed from the map
+ assertFalse(lockMap.containsKey(key));
+ }
+
+ @Test
+ public void testConcurrentLocking() throws InterruptedException {
+ ResourceLock resourceLock = new ResourceLock();
+
+ Thread t1 = new Thread(() -> {
+ try (ResourceLock lock = resourceLock.obtain()) {
+ try {
+ Thread.sleep(100); // Hold the lock for 100ms
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ });
+
+ Thread t2 = new Thread(() -> {
+ assertFalse(resourceLock.isHeldByCurrentThread(), "Lock should not be held by current thread before t1 releases it");
+ try (ResourceLock lock = resourceLock.obtain()) {
+ assertTrue(resourceLock.isHeldByCurrentThread(), "Lock should be held by current thread after t1 releases it");
+ }
+ });
+
+ t1.start();
+ t2.start();
+
+ t1.join();
+ t2.join();
+
+ assertFalse(resourceLock.isHeldByCurrentThread(), "Lock should be released after both threads complete");
+ }
+
+ @Test
+ public void testLockInterruptibly() throws InterruptedException {
+ ResourceLock resourceLock = new ResourceLock();
+
+ Thread t1 = new Thread(() -> {
+ try (ResourceLock lock = resourceLock.obtain()) {
+ try {
+ Thread.sleep(1000); // Hold the lock for 1000ms
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ });
+
+ t1.start();
+ Thread.sleep(50); // Wait for t1 to acquire the lock
+
+ Thread t2 = new Thread(() -> {
+ try {
+ resourceLock.lockInterruptibly();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ });
+
+ t2.start();
+ Thread.sleep(50); // Wait for t2 to attempt to acquire the lock
+
+ t2.interrupt(); // Interrupt t2
+
+ t1.join();
+ t2.join();
+
+ assertFalse(resourceLock.isHeldByCurrentThread(), "Lock should be released after t1 completes");
+ }
+}
diff --git a/discovery/seata-discovery-eureka/src/main/java/org/apache/seata/discovery/registry/eureka/EurekaRegistryServiceImpl.java b/discovery/seata-discovery-eureka/src/main/java/org/apache/seata/discovery/registry/eureka/EurekaRegistryServiceImpl.java
index 5ab5191234d..ef441c34bb1 100644
--- a/discovery/seata-discovery-eureka/src/main/java/org/apache/seata/discovery/registry/eureka/EurekaRegistryServiceImpl.java
+++ b/discovery/seata-discovery-eureka/src/main/java/org/apache/seata/discovery/registry/eureka/EurekaRegistryServiceImpl.java
@@ -26,6 +26,7 @@
import com.netflix.discovery.EurekaEventListener;
import com.netflix.discovery.shared.Application;
import org.apache.seata.common.exception.EurekaRegistryException;
+import org.apache.seata.common.lock.ResourceLock;
import org.apache.seata.common.util.CollectionUtils;
import org.apache.seata.common.util.NetUtil;
import org.apache.seata.common.util.StringUtils;
@@ -68,7 +69,7 @@ public class EurekaRegistryServiceImpl implements RegistryService> LISTENER_SERVICE_MAP = new ConcurrentHashMap<>();
private static final ConcurrentMap> CLUSTER_ADDRESS_MAP = new ConcurrentHashMap<>();
- private static final ConcurrentMap CLUSTER_LOCK = new ConcurrentHashMap<>();
+ private static final ConcurrentMap CLUSTER_LOCK = new ConcurrentHashMap<>();
private static volatile ApplicationInfoManager applicationInfoManager;
private static volatile CustomEurekaInstanceConfig instanceConfig;
@@ -140,8 +141,8 @@ public List lookup(String key) throws Exception {
}
String clusterUpperName = clusterName.toUpperCase();
if (!LISTENER_SERVICE_MAP.containsKey(clusterUpperName)) {
- Object lock = CLUSTER_LOCK.computeIfAbsent(clusterUpperName, k -> new Object());
- synchronized (lock) {
+ ResourceLock lock = CLUSTER_LOCK.computeIfAbsent(clusterUpperName, k -> new ResourceLock());
+ try (ResourceLock ignored = lock.obtain()) {
if (!LISTENER_SERVICE_MAP.containsKey(clusterUpperName)) {
refreshCluster(clusterUpperName);
subscribe(clusterUpperName, event -> {
diff --git a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/hook/TccHookManager.java b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/hook/TccHookManager.java
index e6d537c73f2..4a1048c40c2 100644
--- a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/hook/TccHookManager.java
+++ b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/hook/TccHookManager.java
@@ -20,11 +20,14 @@
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
+import org.apache.seata.common.lock.ResourceLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class TccHookManager {
private static final Logger LOGGER = LoggerFactory.getLogger(TccHookManager.class);
+ private static final ResourceLock LOCK = new ResourceLock();
+
private TccHookManager() {
@@ -40,7 +43,7 @@ private TccHookManager() {
*/
public static List getHooks() {
if (CACHED_UNMODIFIABLE_HOOKS == null) {
- synchronized (TccHookManager.class) {
+ try (ResourceLock ignored = LOCK.obtain()) {
if (CACHED_UNMODIFIABLE_HOOKS == null) {
CACHED_UNMODIFIABLE_HOOKS = Collections.unmodifiableList(TCC_HOOKS);
}
diff --git a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/remoting/parser/DefaultRemotingParser.java b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/remoting/parser/DefaultRemotingParser.java
index 0ed9625e616..9a1f8d307d0 100644
--- a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/remoting/parser/DefaultRemotingParser.java
+++ b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/remoting/parser/DefaultRemotingParser.java
@@ -23,6 +23,7 @@
import org.apache.seata.common.exception.FrameworkException;
import org.apache.seata.common.loader.EnhancedServiceLoader;
+import org.apache.seata.common.lock.ResourceLock;
import org.apache.seata.common.util.CollectionUtils;
import org.apache.seata.integration.tx.api.remoting.RemotingDesc;
import org.apache.seata.integration.tx.api.remoting.RemotingParser;
@@ -43,6 +44,8 @@ public class DefaultRemotingParser {
*/
protected static Map