View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.lang3.concurrent.locks;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertNotNull;
21  import static org.junit.jupiter.api.Assertions.assertNotSame;
22  import static org.junit.jupiter.api.Assertions.assertTrue;
23  
24  import java.time.Duration;
25  import java.util.concurrent.atomic.AtomicInteger;
26  import java.util.concurrent.locks.ReadWriteLock;
27  import java.util.concurrent.locks.ReentrantLock;
28  import java.util.concurrent.locks.ReentrantReadWriteLock;
29  import java.util.concurrent.locks.StampedLock;
30  import java.util.function.LongConsumer;
31  
32  import org.apache.commons.lang3.AbstractLangTest;
33  import org.apache.commons.lang3.ArrayUtils;
34  import org.apache.commons.lang3.ThreadUtils;
35  import org.apache.commons.lang3.concurrent.locks.LockingVisitors.LockVisitor;
36  import org.apache.commons.lang3.concurrent.locks.LockingVisitors.ReadWriteLockVisitor;
37  import org.apache.commons.lang3.concurrent.locks.LockingVisitors.ReentrantLockVisitor;
38  import org.apache.commons.lang3.concurrent.locks.LockingVisitors.StampedLockVisitor;
39  import org.apache.commons.lang3.function.FailableConsumer;
40  import org.junit.jupiter.api.Test;
41  import org.junit.jupiter.params.ParameterizedTest;
42  import org.junit.jupiter.params.provider.ValueSource;
43  
44  /**
45   * Tests {@link LockingVisitors}.
46   */
47  class LockingVisitorsTest extends AbstractLangTest {
48  
49      private static final Duration SHORT_DELAY = Duration.ofMillis(100);
50      private static final Duration DELAY = Duration.ofMillis(1500);
51      private static final int NUMBER_OF_THREADS = 10;
52      private static final Duration TOTAL_DELAY = DELAY.multipliedBy(NUMBER_OF_THREADS);
53  
54      protected boolean containsTrue(final boolean[] booleanArray) {
55          synchronized (booleanArray) {
56              return ArrayUtils.contains(booleanArray, true);
57          }
58      }
59  
60      private void runTest(final Duration delay, final boolean exclusiveLock, final LongConsumer runTimeCheck,
61              final boolean[] booleanValues, final LockVisitor<boolean[], ?> visitor) throws InterruptedException {
62          assertNotNull(visitor.getLock());
63          assertNotNull(visitor.getObject());
64          final boolean[] runningValues = new boolean[10];
65          // final long startTimeMillis = System.currentTimeMillis();
66          for (int i = 0; i < booleanValues.length; i++) {
67              final int index = i;
68              final FailableConsumer<boolean[], ?> consumer = b -> {
69                  b[index] = false;
70                  ThreadUtils.sleep(delay);
71                  b[index] = true;
72                  set(runningValues, index, false);
73              };
74              final Thread t = new Thread(() -> {
75                  if (exclusiveLock) {
76                      visitor.acceptWriteLocked(consumer);
77                  } else {
78                      visitor.acceptReadLocked(consumer);
79                  }
80              });
81              set(runningValues, i, true);
82              t.start();
83          }
84          while (containsTrue(runningValues)) {
85              ThreadUtils.sleep(SHORT_DELAY);
86          }
87          // final long endTimeMillis = System.currentTimeMillis();
88          for (final boolean booleanValue : booleanValues) {
89              assertTrue(booleanValue);
90          }
91          // WRONG assumption
92          // runTimeCheck.accept(endTimeMillis - startTimeMillis);
93      }
94  
95      protected void set(final boolean[] booleanArray, final int offset, final boolean value) {
96          synchronized (booleanArray) {
97              booleanArray[offset] = value;
98          }
99      }
100 
101     @ParameterizedTest
102     @ValueSource(booleans = { true, false })
103     void testBuilderLockVisitor(final boolean fair) {
104         final AtomicInteger obj = new AtomicInteger();
105         final ReadWriteLock lock = new ReentrantReadWriteLock(fair);
106         // @formatter:off
107         final LockVisitor<AtomicInteger, ReadWriteLock> lockVisitor = new LockVisitor.LVBuilder()
108           .setObject(obj)
109           .setLock(lock)
110           .setReadLockSupplier(lock::readLock)
111           .setWriteLockSupplier(lock::writeLock)
112           .get();
113         // @formatter:on
114         lockVisitor.acceptReadLocked(AtomicInteger::incrementAndGet);
115         assertEquals(1, obj.get());
116         lockVisitor.acceptReadLocked(AtomicInteger::incrementAndGet);
117         assertEquals(2, obj.get());
118     }
119 
120     @ParameterizedTest
121     @ValueSource(booleans = { true, false })
122     void testBuilderReadWriteLockVisitor(final boolean fair) {
123         final AtomicInteger obj = new AtomicInteger();
124         final ReadWriteLock lock = new ReentrantReadWriteLock(fair);
125         // @formatter:off
126         final LockingVisitors.ReadWriteLockVisitor<AtomicInteger> lockVisitor = ReadWriteLockVisitor.<AtomicInteger>builder()
127           .setObject(obj)
128           .setLock(lock)
129           .get();
130         // @formatter:on
131         lockVisitor.acceptReadLocked(AtomicInteger::incrementAndGet);
132         assertEquals(1, obj.get());
133         lockVisitor.acceptReadLocked(AtomicInteger::incrementAndGet);
134         assertEquals(2, obj.get());
135     }
136 
137     @ParameterizedTest
138     @ValueSource(booleans = { true, false })
139     void testBuilderReentrantLockVisitor(final boolean fair) {
140         final AtomicInteger obj = new AtomicInteger();
141         final ReentrantLock lock = new ReentrantLock(fair);
142         // @formatter:off
143         final LockingVisitors.ReentrantLockVisitor<AtomicInteger> lockVisitor = ReentrantLockVisitor.<AtomicInteger>builder()
144           .setObject(obj)
145           .setLock(lock)
146           .get();
147         // @formatter:on
148         lockVisitor.acceptReadLocked(AtomicInteger::incrementAndGet);
149         assertEquals(1, obj.get());
150         lockVisitor.acceptReadLocked(AtomicInteger::incrementAndGet);
151         assertEquals(2, obj.get());
152     }
153 
154     @ParameterizedTest
155     @ValueSource(booleans = { true, false })
156     void testBuilderReentrantReadWriteLockVisitor(final boolean fair) {
157         final AtomicInteger obj = new AtomicInteger();
158         final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(fair);
159         // @formatter:off
160         final LockingVisitors.ReadWriteLockVisitor<AtomicInteger> lockVisitor = ReadWriteLockVisitor.<AtomicInteger>builder()
161           .setObject(obj)
162           .setLock(lock)
163           .get();
164         // @formatter:on
165         lockVisitor.acceptReadLocked(AtomicInteger::incrementAndGet);
166         assertEquals(1, obj.get());
167         lockVisitor.acceptReadLocked(AtomicInteger::incrementAndGet);
168         assertEquals(2, obj.get());
169     }
170 
171     @Test
172     void testBuilderReentrantStampedLockVisitor() {
173         final AtomicInteger obj = new AtomicInteger();
174         final StampedLock lock = new StampedLock();
175         // @formatter:off
176         final LockingVisitors.StampedLockVisitor<AtomicInteger> lockVisitor = StampedLockVisitor.<AtomicInteger>builder()
177           .setObject(obj)
178           .setLock(lock)
179           .get();
180         // @formatter:on
181         lockVisitor.acceptReadLocked(AtomicInteger::incrementAndGet);
182         assertEquals(1, obj.get());
183         lockVisitor.acceptReadLocked(AtomicInteger::incrementAndGet);
184         assertEquals(2, obj.get());
185     }
186 
187     @Test
188     void testCreate() {
189         final AtomicInteger obj = new AtomicInteger();
190         final ReadWriteLock lock = new ReentrantReadWriteLock();
191         LockingVisitors.create(obj, lock).acceptReadLocked(AtomicInteger::incrementAndGet);
192         LockingVisitors.create(obj, lock).acceptReadLocked(null);
193         assertEquals(1, obj.get());
194         LockingVisitors.create(obj, lock).acceptWriteLocked(AtomicInteger::incrementAndGet);
195         LockingVisitors.create(obj, lock).acceptWriteLocked(null);
196         assertEquals(2, obj.get());
197     }
198 
199     @SuppressWarnings("deprecation")
200     @Test
201     void testDeprecatedConstructor() {
202         assertNotNull(new LockingVisitors().toString());
203     }
204 
205     @Test
206     void testReentrantLock() throws Exception {
207         // If our threads are running concurrently, then we expect to be faster than running one after the other.
208         final boolean[] booleanValues = new boolean[10];
209         runTest(DELAY, false, millis -> assertTrue(millis < TOTAL_DELAY.toMillis()), booleanValues, LockingVisitors.reentrantLockVisitor(booleanValues));
210     }
211 
212     @ParameterizedTest
213     @ValueSource(booleans = { true, false })
214     void testReentrantLockFairness(final boolean fairness) throws Exception {
215         // If our threads are running concurrently, then we expect to be faster than running one after the other.
216         final boolean[] booleanValues = new boolean[10];
217         runTest(DELAY, false, millis -> assertTrue(millis < TOTAL_DELAY.toMillis()), booleanValues,
218                 LockingVisitors.create(booleanValues, new ReentrantLock(fairness)));
219     }
220 
221     @Test
222     void testReentrantReadWriteLockExclusive() throws Exception {
223         // If our threads are running concurrently, then we expect to be no faster than running one after the other.
224         final boolean[] booleanValues = new boolean[10];
225         runTest(DELAY, true, millis -> assertTrue(millis >= TOTAL_DELAY.toMillis()), booleanValues,
226                 LockingVisitors.reentrantReadWriteLockVisitor(booleanValues));
227     }
228 
229     @Test
230     void testReentrantReadWriteLockNotExclusive() throws Exception {
231         // If our threads are running concurrently, then we expect to be faster than running one after the other.
232         final boolean[] booleanValues = new boolean[10];
233         runTest(DELAY, false, millis -> assertTrue(millis < TOTAL_DELAY.toMillis()), booleanValues,
234                 LockingVisitors.reentrantReadWriteLockVisitor(booleanValues));
235     }
236 
237     @Test
238     void testResultValidation() {
239         final Object hidden = new Object();
240         final StampedLockVisitor<Object> lock = LockingVisitors.stampedLockVisitor(hidden);
241         final Object o1 = lock.applyReadLocked(h -> new Object());
242         assertNotNull(o1);
243         assertNotSame(hidden, o1);
244         final Object o2 = lock.applyWriteLocked(h -> new Object());
245         assertNotNull(o2);
246         assertNotSame(hidden, o2);
247     }
248 
249     @Test
250     void testStampedLockExclusive() throws Exception {
251         // If our threads are running concurrently, then we expect to be no faster than running one after the other.
252         final boolean[] booleanValues = new boolean[10];
253         runTest(DELAY, true, millis -> assertTrue(millis >= TOTAL_DELAY.toMillis()), booleanValues, LockingVisitors.stampedLockVisitor(booleanValues));
254     }
255 
256     @Test
257     void testStampedLockNotExclusive() throws Exception {
258         // If our threads are running concurrently, then we expect to be faster than running one after the other.
259         final boolean[] booleanValues = new boolean[10];
260         runTest(DELAY, false, millis -> assertTrue(millis < TOTAL_DELAY.toMillis()), booleanValues, LockingVisitors.stampedLockVisitor(booleanValues));
261     }
262 }