Skip to content

Commit 8e72147

Browse files
committed
refactor: 优化lock
1 parent b9cf1b3 commit 8e72147

5 files changed

Lines changed: 93 additions & 99 deletions

File tree

jetlinks-components/common-component/src/main/java/org/jetlinks/community/lock/DefaultReactiveLock.java

Lines changed: 92 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,9 @@
1-
/*
2-
* Copyright 2025 JetLinks https://www.jetlinks.cn
3-
*
4-
* Licensed under the Apache License, Version 2.0 (the "License");
5-
* you may not use this file except in compliance with the License.
6-
* You may obtain a copy of the License at
7-
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
9-
*
10-
* Unless required by applicable law or agreed to in writing, software
11-
* distributed under the License is distributed on an "AS IS" BASIS,
12-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13-
* See the License for the specific language governing permissions and
14-
* limitations under the License.
15-
*/
161
package org.jetlinks.community.lock;
172

3+
import lombok.AllArgsConstructor;
4+
import lombok.EqualsAndHashCode;
5+
import lombok.Getter;
6+
import lombok.extern.slf4j.Slf4j;
187
import org.reactivestreams.Publisher;
198
import org.reactivestreams.Subscription;
209
import reactor.core.CoreSubscriber;
@@ -34,6 +23,7 @@
3423
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
3524
import java.util.function.Consumer;
3625

26+
@Slf4j
3727
class DefaultReactiveLock implements ReactiveLock {
3828
@SuppressWarnings("all")
3929
static final AtomicReferenceFieldUpdater<DefaultReactiveLock, LockingSubscriber>
@@ -42,8 +32,22 @@ class DefaultReactiveLock implements ReactiveLock {
4232

4333
final Deque<LockingSubscriber<?>> queue = new ConcurrentLinkedDeque<>();
4434

35+
protected final LockName lockName;
36+
4537
volatile LockingSubscriber<?> pending;
4638

39+
static final AtomicIntegerFieldUpdater<DefaultReactiveLock> WIP =
40+
AtomicIntegerFieldUpdater.newUpdater(DefaultReactiveLock.class, "wip");
41+
42+
volatile int wip;
43+
44+
DefaultReactiveLock(String lockName) {
45+
this.lockName = new LockName(lockName);
46+
}
47+
48+
public boolean isEmpty() {
49+
return queue.isEmpty();
50+
}
4751

4852
@Override
4953
public <T> Flux<T> lock(Flux<T> job) {
@@ -76,28 +80,34 @@ public <T> Mono<T> lock(Mono<T> mono, Duration timeout, Mono<? extends T> fallba
7680
}
7781

7882
protected void drain() {
79-
if (PENDING.get(this) != null) {
83+
if (WIP.getAndIncrement(this) != 0) {
8084
return;
8185
}
8286

8387
LockingSubscriber<?> locking;
8488

85-
for (; ; ) {
86-
locking = queue.pollFirst();
87-
if (locking == null) {
88-
return;
89-
}
90-
if (locking.isDisposed()) {
91-
continue;
92-
}
93-
if (PENDING.compareAndSet(this, null, locking)) {
94-
//使用单独的线程池来调度,防止参与锁太多导致栈溢出.
95-
Schedulers.parallel().schedule(locking::subscribe);
96-
} else {
97-
queue.addLast(locking);
89+
do {
90+
for (; ; ) {
91+
locking = queue.pollFirst();
92+
if (locking == null) {
93+
break;
94+
}
95+
if (locking.isDisposed()) {
96+
continue;
97+
}
98+
if (PENDING.compareAndSet(this, null, locking)) {
99+
try {
100+
locking.subscribe();
101+
} catch (Throwable e) {
102+
PENDING.compareAndSet(this, locking, null);
103+
queue.addLast(locking);
104+
}
105+
} else {
106+
queue.addLast(locking);
107+
}
108+
break;
98109
}
99-
break;
100-
}
110+
} while (WIP.decrementAndGet(this) != 0);
101111

102112
}
103113

@@ -151,6 +161,12 @@ protected LockingFlux(DefaultReactiveLock main, Flux<? extends T> source, Durati
151161

152162
@Override
153163
public void subscribe(@Nonnull CoreSubscriber<? super T> actual) {
164+
if (actual.currentContext().hasKey(main.lockName)) {
165+
log.debug("reactive lock {} already locked in current context, skip.", main.lockName);
166+
//如果当前上下文已经有锁了,则不再重复注册订阅者
167+
source.subscribe(actual);
168+
return;
169+
}
154170
Consumer<CoreSubscriber<? super T>> subscribeCallback = source::subscribe;
155171
main.registerSubscriber(actual, subscribeCallback, timeout, timeoutFallback);
156172
}
@@ -183,14 +199,20 @@ protected LockingMono(DefaultReactiveLock main, Mono<? extends T> source, Durati
183199

184200
@Override
185201
public void subscribe(@Nonnull CoreSubscriber<? super T> actual) {
202+
if (actual.currentContext().hasKey(main.lockName)) {
203+
log.debug("reactive lock {} already locked in current context, skip.", main.lockName);
204+
//如果当前上下文已经有锁了,则不再重复注册订阅者
205+
source.subscribe(actual);
206+
return;
207+
}
186208
Consumer<CoreSubscriber<? super T>> subscribeCallback = source::subscribe;
187209
main.registerSubscriber(actual, subscribeCallback, timeout, fallback);
188210
}
189211

190212

191213
}
192214

193-
static class LockingSubscriber<T> extends BaseSubscriber<T> {
215+
static class LockingSubscriber<T> extends BaseSubscriber<T> implements Runnable {
194216
protected final DefaultReactiveLock main;
195217
protected final CoreSubscriber<? super T> actual;
196218
private final Consumer<CoreSubscriber<? super T>> subscriber;
@@ -201,6 +223,7 @@ static class LockingSubscriber<T> extends BaseSubscriber<T> {
201223
AtomicIntegerFieldUpdater.newUpdater(LockingSubscriber.class, "status");
202224

203225
private volatile int status;
226+
private final Context context;
204227

205228
//初始
206229
private static final int INIT = 0;
@@ -223,6 +246,11 @@ public LockingSubscriber(DefaultReactiveLock main,
223246
this.main = main;
224247
this.subscriber = subscriber;
225248
this.timeoutFallback = timeoutFallback;
249+
this.context = actual
250+
.currentContext()
251+
.put(DefaultReactiveLock.class, main)
252+
.put(main.lockName, true);
253+
226254
if (timeout != null) {
227255
this.timeoutTask = Schedulers
228256
.parallel()
@@ -238,8 +266,12 @@ private void onTimeout() {
238266
if (timeoutFallback != null) {
239267
timeoutFallback.subscribe(actual);
240268
} else {
241-
this.onError(new TimeoutException("Lock timed out"));
269+
Operators.error(
270+
actual, new TimeoutException("Lock [" + main.lockName + "] timeout")
271+
);
242272
}
273+
} else {
274+
main.drain();
243275
}
244276
}
245277

@@ -249,15 +281,21 @@ protected void subscribe() {
249281
timeoutTask.dispose();
250282
}
251283
subscriber.accept(this);
284+
} else {
285+
main.drain();
252286
}
287+
253288
}
254289

255290
protected void complete() {
256-
if (statusUpdater.compareAndSet(this, INIT, UN_SUB) || statusUpdater.compareAndSet(this, SUB_SOURCE, UN_SUB)) {
291+
if (statusUpdater.compareAndSet(this, INIT, UN_SUB)
292+
|| statusUpdater.compareAndSet(this, SUB_SOURCE, UN_SUB)) {
257293
if (timeoutTask != null && !timeoutTask.isDisposed()) {
258294
timeoutTask.dispose();
259295
}
260296
doComplete();
297+
} else {
298+
main.drain();
261299
}
262300
}
263301

@@ -266,9 +304,8 @@ protected void doComplete() {
266304
if (!this.isDisposed()) {
267305
this.cancel();
268306
}
269-
if (PENDING.compareAndSet(main, this, null)) {
270-
main.drain();
271-
}
307+
PENDING.compareAndSet(main, this, null);
308+
main.drain();
272309
}
273310

274311
@Override
@@ -304,8 +341,25 @@ protected final void hookFinally(@Nonnull SignalType type) {
304341
@Override
305342
@Nonnull
306343
public Context currentContext() {
307-
return actual.currentContext();
344+
return context;
345+
}
346+
347+
@Override
348+
public void run() {
349+
subscribe();
308350
}
309351
}
310352

353+
354+
@Getter
355+
@AllArgsConstructor
356+
@EqualsAndHashCode
357+
protected static class LockName {
358+
final String name;
359+
360+
@Override
361+
public String toString() {
362+
return name;
363+
}
364+
}
311365
}
Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,3 @@
1-
/*
2-
* Copyright 2025 JetLinks https://www.jetlinks.cn
3-
*
4-
* Licensed under the Apache License, Version 2.0 (the "License");
5-
* you may not use this file except in compliance with the License.
6-
* You may obtain a copy of the License at
7-
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
9-
*
10-
* Unless required by applicable law or agreed to in writing, software
11-
* distributed under the License is distributed on an "AS IS" BASIS,
12-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13-
* See the License for the specific language governing permissions and
14-
* limitations under the License.
15-
*/
161
package org.jetlinks.community.lock;
172

183
import com.github.benmanes.caffeine.cache.Caffeine;
@@ -30,6 +15,6 @@ class DefaultReactiveLockManager implements ReactiveLockManager {
3015

3116
@Override
3217
public ReactiveLock getLock(String name) {
33-
return cache.computeIfAbsent(name, ignore -> new DefaultReactiveLock());
18+
return cache.computeIfAbsent(name, DefaultReactiveLock::new);
3419
}
3520
}

jetlinks-components/common-component/src/main/java/org/jetlinks/community/lock/ReactiveLock.java

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,3 @@
1-
/*
2-
* Copyright 2025 JetLinks https://www.jetlinks.cn
3-
*
4-
* Licensed under the Apache License, Version 2.0 (the "License");
5-
* you may not use this file except in compliance with the License.
6-
* You may obtain a copy of the License at
7-
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
9-
*
10-
* Unless required by applicable law or agreed to in writing, software
11-
* distributed under the License is distributed on an "AS IS" BASIS,
12-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13-
* See the License for the specific language governing permissions and
14-
* limitations under the License.
15-
*/
161
package org.jetlinks.community.lock;
172

183
import reactor.core.publisher.Flux;

jetlinks-components/common-component/src/main/java/org/jetlinks/community/lock/ReactiveLockHolder.java

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,3 @@
1-
/*
2-
* Copyright 2025 JetLinks https://www.jetlinks.cn
3-
*
4-
* Licensed under the Apache License, Version 2.0 (the "License");
5-
* you may not use this file except in compliance with the License.
6-
* You may obtain a copy of the License at
7-
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
9-
*
10-
* Unless required by applicable law or agreed to in writing, software
11-
* distributed under the License is distributed on an "AS IS" BASIS,
12-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13-
* See the License for the specific language governing permissions and
14-
* limitations under the License.
15-
*/
161
package org.jetlinks.community.lock;
172

183
/**

jetlinks-components/common-component/src/main/java/org/jetlinks/community/lock/ReactiveLockManager.java

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,3 @@
1-
/*
2-
* Copyright 2025 JetLinks https://www.jetlinks.cn
3-
*
4-
* Licensed under the Apache License, Version 2.0 (the "License");
5-
* you may not use this file except in compliance with the License.
6-
* You may obtain a copy of the License at
7-
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
9-
*
10-
* Unless required by applicable law or agreed to in writing, software
11-
* distributed under the License is distributed on an "AS IS" BASIS,
12-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13-
* See the License for the specific language governing permissions and
14-
* limitations under the License.
15-
*/
161
package org.jetlinks.community.lock;
172

183
/**

0 commit comments

Comments
 (0)