1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.configuration2.sync;
18
19 import static org.junit.jupiter.api.Assertions.assertEquals;
20 import static org.mockito.Mockito.mock;
21 import static org.mockito.Mockito.verify;
22 import static org.mockito.Mockito.verifyNoMoreInteractions;
23 import static org.mockito.Mockito.when;
24
25 import java.util.Random;
26 import java.util.concurrent.locks.Lock;
27 import java.util.concurrent.locks.ReadWriteLock;
28
29 import org.junit.jupiter.api.Test;
30
31
32
33
34 public class TestReadWriteSynchronizer {
35
36
37
38 private static final class Account {
39
40 private long amount;
41
42
43
44
45
46
47 public void change(final long delta) {
48 amount += delta;
49 }
50
51
52
53
54
55
56 public long getAmount() {
57 return amount;
58 }
59 }
60
61
62
63
64
65 private static final class ReaderThread extends Thread {
66
67 private final Account[] accounts;
68
69
70 private final Synchronizer sync;
71
72
73 private final int numberOfReads;
74
75
76 private volatile int errors;
77
78
79
80
81
82
83
84
85 public ReaderThread(final Synchronizer s, final int readCount, final Account... accs) {
86 accounts = accs;
87 sync = s;
88 numberOfReads = readCount;
89 }
90
91
92
93
94
95
96 public int getErrors() {
97 return errors;
98 }
99
100
101
102
103 @Override
104 public void run() {
105 for (int i = 0; i < numberOfReads; i++) {
106 sync.beginRead();
107 final long sum = sumUpAccounts(accounts);
108 sync.endRead();
109 if (sum != TOTAL_MONEY) {
110 errors++;
111 }
112 }
113 }
114 }
115
116
117
118
119
120
121 private static final class UpdateThread extends Thread {
122
123 private final Synchronizer sync;
124
125
126 private final Account account1;
127
128
129 private final Account account2;
130
131
132 private final Random random;
133
134
135 private final int numberOfUpdates;
136
137
138
139
140
141
142
143
144
145 public UpdateThread(final Synchronizer s, final int updateCount, final Account ac1, final Account ac2) {
146 sync = s;
147 account1 = ac1;
148 account2 = ac2;
149 numberOfUpdates = updateCount;
150 random = new Random();
151 }
152
153
154
155
156 @Override
157 public void run() {
158 for (int i = 0; i < numberOfUpdates; i++) {
159 sync.beginWrite();
160 final Account acSource;
161 Account acDest;
162 if (account1.getAmount() < account2.getAmount()) {
163 acSource = account1;
164 acDest = account2;
165 } else {
166 acSource = account2;
167 acDest = account1;
168 }
169 final long x = Math.round(random.nextDouble() * (acSource.getAmount() - 1)) + 1;
170 acSource.change(-x);
171 acDest.change(x);
172 sync.endWrite();
173 }
174 }
175 }
176
177
178 private static final long TOTAL_MONEY = 1000000L;
179
180
181
182
183
184
185
186 private static long sumUpAccounts(final Account... accounts) {
187 long sum = 0;
188 for (final Account acc : accounts) {
189 sum += acc.getAmount();
190 }
191 return sum;
192 }
193
194
195
196
197 @Test
198 public void testInitLock() {
199 final ReadWriteLock lock = mock(ReadWriteLock.class);
200 final Lock readLock = mock(Lock.class);
201
202 when(lock.readLock()).thenReturn(readLock);
203
204 final ReadWriteSynchronizer sync = new ReadWriteSynchronizer(lock);
205 sync.beginRead();
206
207 verify(lock).readLock();
208 verify(readLock).lock();
209 verifyNoMoreInteractions(lock, readLock);
210 }
211
212
213
214
215 @Test
216 public void testReentrance() {
217 final Synchronizer sync = new ReadWriteSynchronizer();
218 sync.beginWrite();
219 sync.beginRead();
220 sync.beginRead();
221 sync.endRead();
222 sync.endRead();
223 sync.beginWrite();
224 sync.endWrite();
225 sync.endWrite();
226 }
227
228
229
230
231
232 @Test
233 public void testSynchronizerInAction() throws InterruptedException {
234 final int numberOfUpdates = 10000;
235 final int numberOfReads = numberOfUpdates / 2;
236 final int readThreadCount = 3;
237 final int updateThreadCount = 2;
238
239 final Synchronizer sync = new ReadWriteSynchronizer();
240 final Account account1 = new Account();
241 final Account account2 = new Account();
242 account1.change(TOTAL_MONEY / 2);
243 account2.change(TOTAL_MONEY / 2);
244
245 final UpdateThread[] updateThreads = new UpdateThread[updateThreadCount];
246 for (int i = 0; i < updateThreads.length; i++) {
247 updateThreads[i] = new UpdateThread(sync, numberOfUpdates, account1, account2);
248 updateThreads[i].start();
249 }
250 final ReaderThread[] readerThreads = new ReaderThread[readThreadCount];
251 for (int i = 0; i < readerThreads.length; i++) {
252 readerThreads[i] = new ReaderThread(sync, numberOfReads, account1, account2);
253 readerThreads[i].start();
254 }
255
256 for (final UpdateThread t : updateThreads) {
257 t.join();
258 }
259 for (final ReaderThread t : readerThreads) {
260 t.join();
261 assertEquals(0, t.getErrors());
262 }
263 sync.beginRead();
264 assertEquals(TOTAL_MONEY, sumUpAccounts(account1, account2));
265 sync.endRead();
266 }
267 }