Skip to content

Commit cbfd3aa

Browse files
omercelikcengolegz
authored andcommitted
Change "synchronized" to reentrant lock for virtual-threads
Fix checkstyles before merge Code cleanup Double-Checked Locking Optimization was used to avoid unnecessary locking overhead.
1 parent 7a5e5d0 commit cbfd3aa

File tree

7 files changed

+207
-101
lines changed

7 files changed

+207
-101
lines changed

binders/kafka-binder/spring-cloud-stream-binder-kafka-streams/src/main/java/org/springframework/cloud/stream/binder/kafka/streams/KafkaStreamsBinderMetrics.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019-2019 the original author or authors.
2+
* Copyright 2019-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@
2424
import java.util.Set;
2525
import java.util.concurrent.Executors;
2626
import java.util.concurrent.ScheduledExecutorService;
27+
import java.util.concurrent.locks.ReentrantLock;
2728
import java.util.function.ToDoubleFunction;
2829

2930
import io.micrometer.core.instrument.FunctionCounter;
@@ -52,6 +53,7 @@
5253
* We will keep this class, as long as we support Boot 2.2.x.
5354
*
5455
* @author Soby Chacko
56+
* @author Omer Celik
5557
* @since 3.0.0
5658
*/
5759
public class KafkaStreamsBinderMetrics {
@@ -84,6 +86,8 @@ public class KafkaStreamsBinderMetrics {
8486

8587
private volatile Set<MetricName> currentMeters = new HashSet<>();
8688

89+
private static final ReentrantLock metricsLock = new ReentrantLock();
90+
8791
public KafkaStreamsBinderMetrics(MeterRegistry meterRegistry) {
8892
this.meterRegistry = meterRegistry;
8993
}
@@ -108,9 +112,13 @@ public void bindTo(Set<StreamsBuilderFactoryBean> streamsBuilderFactoryBeans) {
108112
}
109113

110114
public void addMetrics(Set<StreamsBuilderFactoryBean> streamsBuilderFactoryBeans) {
111-
synchronized (KafkaStreamsBinderMetrics.this) {
115+
try {
116+
metricsLock.lock();
112117
this.bindTo(streamsBuilderFactoryBeans);
113118
}
119+
finally {
120+
metricsLock.unlock();
121+
}
114122
}
115123

116124
void prepareToBindMetrics(MeterRegistry registry, Map<MetricName, ? extends Metric> metrics) {

binders/kafka-binder/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaBinderMetrics.java

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.util.concurrent.ScheduledThreadPoolExecutor;
3131
import java.util.concurrent.TimeUnit;
3232
import java.util.concurrent.TimeoutException;
33+
import java.util.concurrent.locks.ReentrantLock;
3334
import java.util.function.ToDoubleFunction;
3435

3536
import io.micrometer.core.instrument.Gauge;
@@ -68,6 +69,7 @@
6869
* @author Tomek Szmytka
6970
* @author Nico Heller
7071
* @author Kurt Hong
72+
* @author Omer Celik
7173
*/
7274
public class KafkaBinderMetrics
7375
implements MeterBinder, ApplicationListener<BindingCreatedEvent>, AutoCloseable {
@@ -97,6 +99,8 @@ public class KafkaBinderMetrics
9799

98100
ScheduledExecutorService scheduler;
99101

102+
private final ReentrantLock consumerFactoryLock = new ReentrantLock();
103+
100104
public KafkaBinderMetrics(KafkaMessageChannelBinder binder,
101105
KafkaBinderConfigurationProperties binderConfigurationProperties,
102106
ConsumerFactory<?, ?> defaultConsumerFactory,
@@ -231,25 +235,36 @@ else if (beginningOffset != null) {
231235
return lag;
232236
}
233237

234-
private synchronized ConsumerFactory<?, ?> createConsumerFactory() {
238+
/**
239+
* Double-Checked Locking Optimization was used to avoid unnecessary locking overhead.
240+
*/
241+
private ConsumerFactory<?, ?> createConsumerFactory() {
235242
if (this.defaultConsumerFactory == null) {
236-
Map<String, Object> props = new HashMap<>();
237-
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
238-
ByteArrayDeserializer.class);
239-
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
240-
ByteArrayDeserializer.class);
241-
Map<String, Object> mergedConfig = this.binderConfigurationProperties
242-
.mergedConsumerConfiguration();
243-
if (!ObjectUtils.isEmpty(mergedConfig)) {
244-
props.putAll(mergedConfig);
245-
}
246-
if (!props.containsKey(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG)) {
247-
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
248-
this.binderConfigurationProperties
243+
try {
244+
this.consumerFactoryLock.lock();
245+
if (this.defaultConsumerFactory == null) {
246+
Map<String, Object> props = new HashMap<>();
247+
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
248+
ByteArrayDeserializer.class);
249+
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
250+
ByteArrayDeserializer.class);
251+
Map<String, Object> mergedConfig = this.binderConfigurationProperties
252+
.mergedConsumerConfiguration();
253+
if (!ObjectUtils.isEmpty(mergedConfig)) {
254+
props.putAll(mergedConfig);
255+
}
256+
if (!props.containsKey(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG)) {
257+
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
258+
this.binderConfigurationProperties
249259
.getKafkaConnectionString());
260+
}
261+
this.defaultConsumerFactory = new DefaultKafkaConsumerFactory<>(
262+
props);
263+
}
264+
}
265+
finally {
266+
this.consumerFactoryLock.unlock();
250267
}
251-
this.defaultConsumerFactory = new DefaultKafkaConsumerFactory<>(
252-
props);
253268
}
254269
return this.defaultConsumerFactory;
255270
}

binders/rabbit-binder/spring-cloud-stream-binder-rabbit-core/src/main/java/org/springframework/cloud/stream/binder/rabbit/provisioning/RabbitExchangeQueueProvisioner.java

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2023 the original author or authors.
2+
* Copyright 2016-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@
2222
import java.util.List;
2323
import java.util.Map;
2424
import java.util.concurrent.atomic.AtomicInteger;
25+
import java.util.concurrent.locks.ReentrantLock;
2526
import java.util.stream.Collectors;
2627
import java.util.stream.IntStream;
2728
import java.util.stream.Stream;
@@ -79,6 +80,7 @@
7980
* @author Oleg Zhurakousky
8081
* @author Michael Michailidis
8182
* @author Byungjun You
83+
* @author Omer Celik
8284
*/
8385
// @checkstyle:off
8486
public class RabbitExchangeQueueProvisioner
@@ -105,6 +107,8 @@ public class RabbitExchangeQueueProvisioner
105107

106108
private final AtomicInteger producerExchangeBeanNameQualifier = new AtomicInteger();
107109

110+
private final ReentrantLock autoDeclareContextLock = new ReentrantLock();
111+
108112
public RabbitExchangeQueueProvisioner(ConnectionFactory connectionFactory) {
109113
this(connectionFactory, Collections.emptyList());
110114
}
@@ -320,11 +324,15 @@ private void provisionSuperStream(ExtendedConsumerProperties<RabbitConsumerPrope
320324
(q, i) -> IntStream.range(0, i)
321325
.mapToObj(j -> rk + "-" + j)
322326
.collect(Collectors.toList()));
323-
synchronized (this.autoDeclareContext) {
327+
try {
328+
this.autoDeclareContextLock.lock();
324329
if (!this.autoDeclareContext.containsBean(name + ".superStream")) {
325330
this.autoDeclareContext.getBeanFactory().registerSingleton(name + ".superStream", ss);
326331
}
327332
}
333+
finally {
334+
this.autoDeclareContextLock.unlock();
335+
}
328336
try {
329337
ss.getDeclarables().forEach(dec -> {
330338
if (dec instanceof Exchange exch) {
@@ -716,14 +724,18 @@ private Exchange customizeAndDeclare(Exchange exchange) {
716724
}
717725

718726
private void addToAutoDeclareContext(String name, Declarable bean) {
719-
synchronized (this.autoDeclareContext) {
727+
try {
728+
this.autoDeclareContextLock.lock();
720729
if (!this.autoDeclareContext.containsBean(name)) {
721730
this.autoDeclareContext.getBeanFactory().registerSingleton(name, new Declarables(bean));
722731
}
723732
else {
724733
this.autoDeclareContext.getBean(name, Declarables.class).getDeclarables().add(bean);
725734
}
726735
}
736+
finally {
737+
this.autoDeclareContextLock.unlock();
738+
}
727739
}
728740

729741
private void declareBinding(String rootName, org.springframework.amqp.core.Binding bindingArg) {
@@ -756,31 +768,36 @@ private void declareBinding(String rootName, org.springframework.amqp.core.Bindi
756768
public void cleanAutoDeclareContext(ConsumerDestination destination,
757769
ExtendedConsumerProperties<RabbitConsumerProperties> consumerProperties) {
758770

759-
synchronized (this.autoDeclareContext) {
771+
try {
772+
this.autoDeclareContextLock.lock();
760773
Stream.of(StringUtils.tokenizeToStringArray(destination.getName(), ",", true,
761-
true)).forEach(name -> {
762-
String group = null;
763-
String bindingName = null;
764-
if (destination instanceof RabbitConsumerDestination rabbitConsumerDestination) {
765-
group = rabbitConsumerDestination.getGroup();
766-
bindingName = rabbitConsumerDestination.getBindingName();
767-
}
768-
RabbitConsumerProperties properties = consumerProperties.getExtension();
769-
String toRemove = properties.isQueueNameGroupOnly() ? bindingName + "." + group : name.trim();
770-
boolean partitioned = consumerProperties.isPartitioned();
771-
if (partitioned) {
772-
toRemove = removePartitionPart(toRemove);
773-
}
774-
removeSingleton(toRemove + ".exchange");
775-
removeQueueAndBindingBeans(properties, name.trim(), "", group, partitioned);
776-
});
774+
true)).forEach(name -> {
775+
String group = null;
776+
String bindingName = null;
777+
if (destination instanceof RabbitConsumerDestination rabbitConsumerDestination) {
778+
group = rabbitConsumerDestination.getGroup();
779+
bindingName = rabbitConsumerDestination.getBindingName();
780+
}
781+
RabbitConsumerProperties properties = consumerProperties.getExtension();
782+
String toRemove = properties.isQueueNameGroupOnly() ? bindingName + "." + group : name.trim();
783+
boolean partitioned = consumerProperties.isPartitioned();
784+
if (partitioned) {
785+
toRemove = removePartitionPart(toRemove);
786+
}
787+
removeSingleton(toRemove + ".exchange");
788+
removeQueueAndBindingBeans(properties, name.trim(), "", group, partitioned);
789+
});
790+
}
791+
finally {
792+
this.autoDeclareContextLock.unlock();
777793
}
778794
}
779795

780796
public void cleanAutoDeclareContext(ProducerDestination dest,
781797
ExtendedProducerProperties<RabbitProducerProperties> properties) {
782798

783-
synchronized (this.autoDeclareContext) {
799+
try {
800+
this.autoDeclareContextLock.lock();
784801
if (dest instanceof RabbitProducerDestination rabbitProducerDestination) {
785802
String qual = rabbitProducerDestination.getBeanNameQualifier();
786803
removeSingleton(dest.getName() + "." + qual + ".exchange");
@@ -790,13 +807,13 @@ public void cleanAutoDeclareContext(ProducerDestination dest,
790807
if (properties.isPartitioned()) {
791808
for (int i = 0; i < properties.getPartitionCount(); i++) {
792809
removeQueueAndBindingBeans(properties.getExtension(),
793-
properties.getExtension().isQueueNameGroupOnly() ? "" : dest.getName(),
794-
group + "-" + i, group, true);
810+
properties.getExtension().isQueueNameGroupOnly() ? "" : dest.getName(),
811+
group + "-" + i, group, true);
795812
}
796813
}
797814
else {
798815
removeQueueAndBindingBeans(properties.getExtension(), dest.getName() + "." + group, "",
799-
group, false);
816+
group, false);
800817
}
801818
}
802819
}
@@ -811,6 +828,9 @@ public void cleanAutoDeclareContext(ProducerDestination dest,
811828
}
812829
}
813830
}
831+
finally {
832+
this.autoDeclareContextLock.unlock();
833+
}
814834
}
815835

816836
private void removeQueueAndBindingBeans(RabbitCommonProperties properties, String name, String suffix,

core/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindableProxyFactory.java

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2015-2022 the original author or authors.
2+
* Copyright 2015-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
1919
import java.lang.reflect.Method;
2020
import java.util.HashMap;
2121
import java.util.Map;
22+
import java.util.concurrent.locks.ReentrantLock;
2223

2324
import org.aopalliance.intercept.MethodInterceptor;
2425
import org.aopalliance.intercept.MethodInvocation;
@@ -43,6 +44,7 @@
4344
* @author Ilayaperumal Gopinathan
4445
* @author Oleg Zhurakousky
4546
* @author Soby Chacko
47+
* @author Omer Celik
4648
*/
4749
public class BindableProxyFactory extends AbstractBindableProxyFactory
4850
implements MethodInterceptor, FactoryBean<Object>, InitializingBean, BeanFactoryAware {
@@ -55,21 +57,25 @@ public class BindableProxyFactory extends AbstractBindableProxyFactory
5557

5658
protected BeanFactory beanFactory;
5759

60+
private final ReentrantLock lock = new ReentrantLock();
61+
5862
public BindableProxyFactory(Class<?> type) {
5963
super(type);
6064
this.type = type;
6165
}
6266

6367
@Override
64-
public synchronized Object invoke(MethodInvocation invocation) {
65-
Method method = invocation.getMethod();
68+
public Object invoke(MethodInvocation invocation) {
69+
try {
70+
this.lock.lock();
71+
Method method = invocation.getMethod();
6672

67-
// try to use cached target
68-
Object boundTarget = this.targetCache.get(method);
69-
if (boundTarget != null) {
70-
return boundTarget;
73+
// try to use cached target
74+
return this.targetCache.get(method);
75+
}
76+
finally {
77+
this.lock.unlock();
7178
}
72-
return null;
7379
}
7480

7581
public void replaceInputChannel(String originalChannelName, String newChannelName, SubscribableChannel messageChannel) {
@@ -97,11 +103,22 @@ public void afterPropertiesSet() {
97103
"'bindingTargetFactories' cannot be empty");
98104
}
99105

106+
/**
107+
* Double-Checked Locking Optimization was used to avoid unnecessary locking overhead.
108+
*/
100109
@Override
101-
public synchronized Object getObject() {
110+
public Object getObject() {
102111
if (this.proxy == null && this.type != null) {
103-
ProxyFactory factory = new ProxyFactory(this.type, this);
104-
this.proxy = factory.getProxy();
112+
try {
113+
this.lock.lock();
114+
if (this.proxy == null && this.type != null) {
115+
ProxyFactory factory = new ProxyFactory(this.type, this);
116+
this.proxy = factory.getProxy();
117+
}
118+
}
119+
finally {
120+
this.lock.unlock();
121+
}
105122
}
106123
return this.proxy;
107124
}

0 commit comments

Comments
 (0)