@@ -31,8 +31,8 @@ abstract class MpmcArrayQueueProducerField<E> extends MpmcArrayQueueL1Pad<E> {
31
31
private final static long P_INDEX_OFFSET ;
32
32
static {
33
33
try {
34
- P_INDEX_OFFSET =
35
- UNSAFE . objectFieldOffset ( MpmcArrayQueueProducerField . class .getDeclaredField ("producerIndex" ));
34
+ P_INDEX_OFFSET = UNSAFE . objectFieldOffset ( MpmcArrayQueueProducerField . class
35
+ .getDeclaredField ("producerIndex" ));
36
36
} catch (NoSuchFieldException e ) {
37
37
throw new RuntimeException (e );
38
38
}
@@ -65,8 +65,8 @@ abstract class MpmcArrayQueueConsumerField<E> extends MpmcArrayQueueL2Pad<E> {
65
65
private final static long C_INDEX_OFFSET ;
66
66
static {
67
67
try {
68
- C_INDEX_OFFSET =
69
- UNSAFE . objectFieldOffset ( MpmcArrayQueueConsumerField . class .getDeclaredField ("consumerIndex" ));
68
+ C_INDEX_OFFSET = UNSAFE . objectFieldOffset ( MpmcArrayQueueConsumerField . class
69
+ .getDeclaredField ("consumerIndex" ));
70
70
} catch (NoSuchFieldException e ) {
71
71
throw new RuntimeException (e );
72
72
}
@@ -87,26 +87,28 @@ protected final boolean casConsumerIndex(long expect, long newValue) {
87
87
}
88
88
89
89
/**
90
- * A Multi-Producer-Multi-Consumer queue based on a {@link ConcurrentCircularArrayQueue}. This implies that any and all
91
- * threads may call the offer/poll/peek methods and correctness is maintained. <br>
90
+ * A Multi-Producer-Multi-Consumer queue based on a {@link ConcurrentCircularArrayQueue}. This implies that
91
+ * any and all threads may call the offer/poll/peek methods and correctness is maintained. <br>
92
92
* This implementation follows patterns documented on the package level for False Sharing protection.<br>
93
93
* The algorithm for offer/poll is an adaptation of the one put forward by D. Vyukov (See <a
94
- * href="http://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queue">here</a>). The original algorithm
95
- * uses an array of structs which should offer nice locality properties but is sadly not possible in Java (waiting on
96
- * Value Types or similar). The alternative explored here utilizes 2 arrays, one for each field of the struct. There is
97
- * a further alternative in the experimental project which uses iteration phase markers to achieve the same algo and is
98
- * closer structurally to the original, but sadly does not perform as well as this implementation.<br>
94
+ * href="http://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queue">here</a>). The original
95
+ * algorithm uses an array of structs which should offer nice locality properties but is sadly not possible in
96
+ * Java (waiting on Value Types or similar). The alternative explored here utilizes 2 arrays, one for each
97
+ * field of the struct. There is a further alternative in the experimental project which uses iteration phase
98
+ * markers to achieve the same algo and is closer structurally to the original, but sadly does not perform as
99
+ * well as this implementation.<br>
99
100
* Tradeoffs to keep in mind:
100
101
* <ol>
101
- * <li>Padding for false sharing: counter fields and queue fields are all padded as well as either side of both arrays.
102
- * We are trading memory to avoid false sharing(active and passive).
103
- * <li>2 arrays instead of one: The algorithm requires an extra array of longs matching the size of the elements array.
104
- * This is doubling/tripling the memory allocated for the buffer.
102
+ * <li>Padding for false sharing: counter fields and queue fields are all padded as well as either side of
103
+ * both arrays. We are trading memory to avoid false sharing(active and passive).
104
+ * <li>2 arrays instead of one: The algorithm requires an extra array of longs matching the size of the
105
+ * elements array. This is doubling/tripling the memory allocated for the buffer.
105
106
* <li>Power of 2 capacity: Actual elements buffer (and sequence buffer) is the closest power of 2 larger or
106
107
* equal to the requested capacity.
107
108
* </ol>
108
109
*
109
- * @param <E> type of the element stored in the {@link java.util.Queue}
110
+ * @param <E>
111
+ * type of the element stored in the {@link java.util.Queue}
110
112
*/
111
113
public class MpmcArrayQueue <E > extends MpmcArrayQueueConsumerField <E > {
112
114
long p40 , p41 , p42 , p43 , p44 , p45 , p46 ;
@@ -123,10 +125,11 @@ public boolean offer(final E e) {
123
125
}
124
126
125
127
// local load of field to avoid repeated loads after volatile reads
128
+ final long capacity = mask + 1 ;
126
129
final long [] lSequenceBuffer = sequenceBuffer ;
127
130
long currentProducerIndex ;
128
131
long seqOffset ;
129
-
132
+ long cIndex = Long . MAX_VALUE ; // start with bogus value, hope we don't need it
130
133
while (true ) {
131
134
currentProducerIndex = lvProducerIndex (); // LoadLoad
132
135
seqOffset = calcSequenceOffset (currentProducerIndex );
@@ -140,8 +143,10 @@ public boolean offer(final E e) {
140
143
break ;
141
144
}
142
145
// failed cas, retry 1
143
- } else if (delta < 0 ) {
144
- // poll has not moved this value forward
146
+ } else if (delta < 0 && // poll has not moved this value forward
147
+ currentProducerIndex - capacity <= cIndex && // test against cached cIndex
148
+ currentProducerIndex - capacity <= (cIndex = lvConsumerIndex ())) { // test against latest cIndex
149
+ // Extra check required to ensure [Queue.offer == false iff queue is full]
145
150
return false ;
146
151
}
147
152
@@ -161,16 +166,17 @@ public boolean offer(final E e) {
161
166
162
167
/**
163
168
* {@inheritDoc}
164
- * Because return null indicates queue is empty we cannot simply rely on next element visibility for poll and must
165
- * test producer index when next element is not visible.
169
+ * <p>
170
+ * Because return null indicates queue is empty we cannot simply rely on next element visibility for poll
171
+ * and must test producer index when next element is not visible.
166
172
*/
167
173
@ Override
168
174
public E poll () {
169
175
// local load of field to avoid repeated loads after volatile reads
170
176
final long [] lSequenceBuffer = sequenceBuffer ;
171
177
long currentConsumerIndex ;
172
178
long seqOffset ;
173
-
179
+ long pIndex = - 1 ; // start with bogus value, hope we don't need it
174
180
while (true ) {
175
181
currentConsumerIndex = lvConsumerIndex ();// LoadLoad
176
182
seqOffset = calcSequenceOffset (currentConsumerIndex );
@@ -183,12 +189,10 @@ public E poll() {
183
189
break ;
184
190
}
185
191
// failed cas, retry 1
186
- } else if (delta < 0 ) {
187
- // COMMENTED OUT: strict empty check.
188
- // if (currentConsumerIndex == lvProducerIndex()) {
189
- // return null;
190
- // }
191
- // next element is not visible, probably empty
192
+ } else if (delta < 0 && // slot has not been moved by producer
193
+ currentConsumerIndex >= pIndex && // test against cached pIndex
194
+ currentConsumerIndex == (pIndex = lvProducerIndex ())) { // update pIndex if we must
195
+ // strict empty check, this ensures [Queue.poll() == null iff isEmpty()]
192
196
return null ;
193
197
}
194
198
@@ -202,22 +206,31 @@ public E poll() {
202
206
203
207
// Move sequence ahead by capacity, preparing it for next offer
204
208
// (seeing this value from a consumer will lead to retry 2)
205
- soSequence (lSequenceBuffer , seqOffset , currentConsumerIndex + capacity );// StoreStore
209
+ soSequence (lSequenceBuffer , seqOffset , currentConsumerIndex + mask + 1 );// StoreStore
206
210
207
211
return e ;
208
212
}
209
213
210
214
@ Override
211
215
public E peek () {
212
- return lpElement (calcElementOffset (lvConsumerIndex ()));
216
+ long currConsumerIndex ;
217
+ E e ;
218
+ do {
219
+ currConsumerIndex = lvConsumerIndex ();
220
+ // other consumers may have grabbed the element, or queue might be empty
221
+ e = lpElement (calcElementOffset (currConsumerIndex ));
222
+ // only return null if queue is empty
223
+ } while (e == null && currConsumerIndex != lvProducerIndex ());
224
+ return e ;
213
225
}
214
226
215
227
@ Override
216
228
public int size () {
217
229
/*
218
- * It is possible for a thread to be interrupted or reschedule between the read of the producer and consumer
219
- * indices, therefore protection is required to ensure size is within valid range. In the event of concurrent
220
- * polls/offers to this method the size is OVER estimated as we read consumer index BEFORE the producer index.
230
+ * It is possible for a thread to be interrupted or reschedule between the read of the producer and
231
+ * consumer indices, therefore protection is required to ensure size is within valid range. In the
232
+ * event of concurrent polls/offers to this method the size is OVER estimated as we read consumer
233
+ * index BEFORE the producer index.
221
234
*/
222
235
long after = lvConsumerIndex ();
223
236
while (true ) {
@@ -229,13 +242,13 @@ public int size() {
229
242
}
230
243
}
231
244
}
232
-
245
+
233
246
@ Override
234
247
public boolean isEmpty () {
235
- // Order matters!
248
+ // Order matters!
236
249
// Loading consumer before producer allows for producer increments after consumer index is read.
237
- // This ensures this method is conservative in it's estimate. Note that as this is an MPMC there is nothing we
238
- // can do to make this an exact method.
250
+ // This ensures this method is conservative in it's estimate. Note that as this is an MPMC there is
251
+ // nothing we can do to make this an exact method.
239
252
return (lvConsumerIndex () == lvProducerIndex ());
240
253
}
241
- }
254
+ }
0 commit comments