From 49bff5eadc8371dd726584ac66e935995afd9287 Mon Sep 17 00:00:00 2001 From: zhoney Date: Fri, 11 Oct 2019 20:07:27 +0800 Subject: [PATCH] Support RowLock in LockGroup (#39) Change-Id: Ic25b7d81b5dd5ddc9d85b057e5f00a00763460c7 --- pom.xml | 4 +- .../baidu/hugegraph/concurrent/LockGroup.java | 14 +++ .../baidu/hugegraph/concurrent/RowLock.java | 106 +++++++++++++++++ .../hugegraph/version/CommonVersion.java | 2 +- .../baidu/hugegraph/unit/UnitTestSuite.java | 2 + .../unit/concurrent/LockGroupTest.java | 17 +++ .../unit/concurrent/RowLockTest.java | 108 ++++++++++++++++++ 7 files changed, 250 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/baidu/hugegraph/concurrent/RowLock.java create mode 100644 src/test/java/com/baidu/hugegraph/unit/concurrent/RowLockTest.java diff --git a/pom.xml b/pom.xml index 8c8fc7d48..034fce13e 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.baidu.hugegraph hugegraph-common - 1.6.15 + 1.6.16 hugegraph-common https://github.com/hugegraph/hugegraph-common @@ -218,7 +218,7 @@ - 1.6.15.0 + 1.6.16.0 diff --git a/src/main/java/com/baidu/hugegraph/concurrent/LockGroup.java b/src/main/java/com/baidu/hugegraph/concurrent/LockGroup.java index 44d064dee..a3f26ef9f 100644 --- a/src/main/java/com/baidu/hugegraph/concurrent/LockGroup.java +++ b/src/main/java/com/baidu/hugegraph/concurrent/LockGroup.java @@ -64,6 +64,20 @@ public KeyLock keyLock(String lockName) { return (KeyLock) this.locksMap.get(lockName); } + public KeyLock keyLock(String lockName, int size) { + if (!this.locksMap.containsKey(lockName)) { + this.locksMap.putIfAbsent(lockName, new KeyLock(size)); + } + return (KeyLock) this.locksMap.get(lockName); + } + + public RowLock rowLock(String lockName) { + if (!this.locksMap.containsKey(lockName)) { + this.locksMap.putIfAbsent(lockName, new RowLock()); + } + return (RowLock) this.locksMap.get(lockName); + } + public String name() { return this.name; } diff --git a/src/main/java/com/baidu/hugegraph/concurrent/RowLock.java b/src/main/java/com/baidu/hugegraph/concurrent/RowLock.java new file mode 100644 index 000000000..6fa5197d0 --- /dev/null +++ b/src/main/java/com/baidu/hugegraph/concurrent/RowLock.java @@ -0,0 +1,106 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.concurrent; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import com.baidu.hugegraph.util.E; + +public class RowLock> { + + private final Map locks = new ConcurrentHashMap<>(); + private final ThreadLocal> localLocks = + ThreadLocal.withInitial(HashMap::new); + + public void lock(K key) { + if (key == null) { + return; + } + LocalLock localLock = this.localLocks.get().get(key); + if (localLock != null) { + localLock.lockCount++; + } else { + Lock current = new ReentrantLock(); + Lock previous = this.locks.putIfAbsent(key, current); + if (previous != null) { + current = previous; + } + current.lock(); + this.localLocks.get().put(key, new LocalLock(current)); + } + } + + public void unlock(K key) { + if (key == null) { + return; + } + LocalLock localLock = this.localLocks.get().get(key); + if (localLock == null) { + return; + } + if (--localLock.lockCount == 0) { + this.locks.remove(key, localLock.current); + this.localLocks.get().remove(key); + localLock.current.unlock(); + } + E.checkState(localLock.lockCount >= 0, + "The lock count must be >= 0, but got %s", + localLock.lockCount); + } + + public void lockAll(Set keys) { + if (keys == null) { + return; + } + List list = new ArrayList<>(keys); + Collections.sort(list); + for (K key : list) { + this.lock(key); + } + } + + public void unlockAll(Set keys) { + if (keys == null) { + return; + } + for (K key : keys) { + this.unlock(key); + } + } + + private static class LocalLock { + + private final Lock current; + private int lockCount; + + private LocalLock(Lock current) { + this.current = current; + this.lockCount = 1; + } + } +} diff --git a/src/main/java/com/baidu/hugegraph/version/CommonVersion.java b/src/main/java/com/baidu/hugegraph/version/CommonVersion.java index 74498bc9b..0e4d2be9f 100644 --- a/src/main/java/com/baidu/hugegraph/version/CommonVersion.java +++ b/src/main/java/com/baidu/hugegraph/version/CommonVersion.java @@ -27,5 +27,5 @@ public class CommonVersion { // The second parameter of Version.of() is for all-in-one JAR public static final Version VERSION = Version.of(CommonVersion.class, - "1.6.15"); + "1.6.16"); } diff --git a/src/test/java/com/baidu/hugegraph/unit/UnitTestSuite.java b/src/test/java/com/baidu/hugegraph/unit/UnitTestSuite.java index ef3f19401..a8d597a6e 100644 --- a/src/test/java/com/baidu/hugegraph/unit/UnitTestSuite.java +++ b/src/test/java/com/baidu/hugegraph/unit/UnitTestSuite.java @@ -24,6 +24,7 @@ import com.baidu.hugegraph.testutil.AssertTest; import com.baidu.hugegraph.testutil.WhiteboxTest; +import com.baidu.hugegraph.unit.concurrent.RowLockTest; import com.baidu.hugegraph.unit.concurrent.LockGroupTest; import com.baidu.hugegraph.unit.config.HugeConfigTest; import com.baidu.hugegraph.unit.date.SafeDateFormatTest; @@ -57,6 +58,7 @@ @RunWith(Suite.class) @Suite.SuiteClasses({ LockGroupTest.class, + RowLockTest.class, HugeConfigTest.class, SafeDateFormatTest.class, EventHubTest.class, diff --git a/src/test/java/com/baidu/hugegraph/unit/concurrent/LockGroupTest.java b/src/test/java/com/baidu/hugegraph/unit/concurrent/LockGroupTest.java index bd830fede..ed7c7425b 100644 --- a/src/test/java/com/baidu/hugegraph/unit/concurrent/LockGroupTest.java +++ b/src/test/java/com/baidu/hugegraph/unit/concurrent/LockGroupTest.java @@ -28,6 +28,7 @@ import com.baidu.hugegraph.concurrent.AtomicLock; import com.baidu.hugegraph.concurrent.KeyLock; +import com.baidu.hugegraph.concurrent.RowLock; import com.baidu.hugegraph.concurrent.LockGroup; import com.baidu.hugegraph.testutil.Assert; @@ -69,6 +70,22 @@ public void testKeyLock() { Assert.assertSame(lock, lock1); } + @Test + public void testKeyLockWithSize() { + KeyLock lock = this.group.keyLock("lock", 10); + Assert.assertNotNull(lock); + KeyLock lock1 = this.group.keyLock("lock"); + Assert.assertSame(lock, lock1); + } + + @Test + public void testRowLock() { + RowLock lock = this.group.rowLock("lock"); + Assert.assertNotNull(lock); + RowLock lock1 = this.group.rowLock("lock"); + Assert.assertSame(lock, lock1); + } + @Test public void testName() { Assert.assertEquals(GROUP, this.group.name()); diff --git a/src/test/java/com/baidu/hugegraph/unit/concurrent/RowLockTest.java b/src/test/java/com/baidu/hugegraph/unit/concurrent/RowLockTest.java new file mode 100644 index 000000000..d7dd15326 --- /dev/null +++ b/src/test/java/com/baidu/hugegraph/unit/concurrent/RowLockTest.java @@ -0,0 +1,108 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.unit.concurrent; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; + +import org.junit.Assert; +import org.junit.Test; + +import com.baidu.hugegraph.concurrent.RowLock; +import com.baidu.hugegraph.unit.BaseUnitTest; +import com.google.common.collect.ImmutableSet; + +public class RowLockTest extends BaseUnitTest { + + private static final int THREADS_NUM = 8; + + @Test + public void testRowLock() { + RowLock lock = new RowLock<>(); + // Regular lock and unlock + lock.lock(1); + lock.unlock(1); + + // Lock one lock multiple times + lock.lock(1); + lock.lock(1); + lock.unlock(1); + lock.unlock(1); + + // Unlock one lock multiple times + lock.lock(1); + lock.unlock(1); + lock.unlock(1); + } + + @Test + public void testRowLockMultiRows() { + RowLock lock = new RowLock<>(); + lock.lockAll(ImmutableSet.of(1, 2, 3)); + lock.unlockAll(ImmutableSet.of(1, 2, 3)); + } + + @SuppressWarnings("unchecked") + @Test + public void testRowLockWithMultiThreads() { + RowLock lock = new RowLock(); + Set names = new HashSet<>(THREADS_NUM); + List keys = new ArrayList<>(5); + Random random = new Random(); + for (int i = 0; i < 5; i++) { + keys.add(random.nextInt(THREADS_NUM)); + } + + Assert.assertEquals(0, names.size()); + + runWithThreads(THREADS_NUM, () -> { + lock.lockAll(new HashSet<>(keys)); + names.add(Thread.currentThread().getName()); + lock.unlockAll(new HashSet<>(keys)); + }); + + Assert.assertEquals(THREADS_NUM, names.size()); + } + + @SuppressWarnings("unchecked") + @Test + public void testRowLockWithMultiThreadsWithRandomKey() { + RowLock lock = new RowLock(); + Set names = new HashSet<>(THREADS_NUM); + + Assert.assertEquals(0, names.size()); + + runWithThreads(THREADS_NUM, () -> { + List keys = new ArrayList<>(5); + Random random = new Random(); + for (int i = 0; i < 5; i++) { + keys.add(random.nextInt(THREADS_NUM)); + } + lock.lockAll(new HashSet<>(keys)); + names.add(Thread.currentThread().getName()); + lock.unlockAll(new HashSet<>(keys)); + }); + + Assert.assertEquals(THREADS_NUM, names.size()); + } +}