io.atlassian.util.concurrent.BlockingReference Maven / Gradle / Ivy
Show all versions of atlassian-util-concurrent Show documentation
/**
* Copyright 2008 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.atlassian.util.concurrent;
import net.jcip.annotations.ThreadSafe;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.Objects.requireNonNull;
/**
* A Reference with queue semantics where the current reference may be retrieved
* or taken instead, and if there is no current element then it will be block
* until the reference becomes available. This is somewhat analogous to a single
* element {@link java.util.concurrent.BlockingQueue}.
*
* Note: this class does not support null elements being {@link #set(Object)}
* and will throw an exception. If the internal reference is null, then calls to
* {@link #take()} or {@link #take(long, TimeUnit)} will block.
*
* This class is most suited to {@link #newSRSW() SRSW} or {@link #newMRSW()
* MRSW} usage. Multiple writers will overwrite each other's elements and the
* chosen value will be arbitrary in the absence of any external consensus. If
* multiple readers are waiting to {@link #take()} a value, one reader will be
* arbitrarily chosen (similar to
* {@link java.util.concurrent.locks.Condition#signal()}). Multiple readers can
* however {@link #get()} the current value if it is not null, but they may see
* the current value more than once. If multiple readers attempt to
* {@link #get()} a value from the SRSW reference and it is not yet present then
* only one waiting thread may be notified, please use the MRSW version for this
* case.
*
* This implementation has been optimized for SRSW performance with
* {@link #set(Object)}/{@link #take()} pairs.
*
* This class is explicit in that it handles take/get separately.
*
* @param the value type
* @see BlockingQueue
*/
@ThreadSafe public class BlockingReference {
//
// static factory methods
//
/**
* Create a BlockingReference best suited to single-reader/single-writer
* situations. In a MRSW case this instance may get missed signals if multiple
* reader threads are all waiting on the value.
*
* @return a {@link io.atlassian.util.concurrent.BlockingReference} object.
*/
public static BlockingReference newSRSW() {
return newSRSW(null);
}
/**
* Create a BlockingReference best suited to single-reader/single-writer
* situations. In a MRSW case this instance may get missed signals if multiple
* reader threads are all waiting on the value.
*
* @param initialValue the initial value
* @param a V value to reference.
* @return a {@link io.atlassian.util.concurrent.BlockingReference}.
*/
public static BlockingReference newSRSW(final V initialValue) {
return new BlockingReference(new BooleanLatch(), initialValue);
}
/**
* Create a BlockingReference best suited to multi-reader/single-writer
* situations. In a SRSW case this instance may not perform quite as well.
*
* @return a {@link io.atlassian.util.concurrent.BlockingReference}.
*/
public static BlockingReference newMRSW() {
return newMRSW(null);
}
/**
* Create a BlockingReference best suited to multi-reader/single-writer
* situations. In a SRSW case this instance may not perform quite as well.
*
* @param initialValue the initial value
* @param a V value to reference.
* @return a {@link io.atlassian.util.concurrent.BlockingReference}.
*/
public static BlockingReference newMRSW(final V initialValue) {
return new BlockingReference(new PhasedLatch() {
/*
* Workaround for the fact that take() always calls await. Calling await()
* on a phased latch by default waits on the next phase (after the
* current one). We need to make sure we await on the previous phase
* instead so we remember the previous phase.
*/
private final AtomicInteger currentPhase = new AtomicInteger(super.getPhase());
@Override public synchronized int getPhase() {
try {
return currentPhase.get();
} finally {
currentPhase.set(super.getPhase());
}
}
}, initialValue);
}
//
// instance vars
//
private final AtomicReference ref = new AtomicReference();
private final ReusableLatch latch;
//
// ctors
//
BlockingReference(final ReusableLatch latch, final V initialValue) {
this.latch = latch;
internalSet(initialValue);
}
// /CLOVER:OFF
/**
* Creates a new SRSW BlockingReference.
*
* @deprecated use {@link #newSRSW()} instead.
*/
@Deprecated public BlockingReference() {
this(new BooleanLatch(), null);
}
/**
* Creates a new SRSW BlockingReference.
*
* @deprecated use {@link #newSRSW()} instead.
* @param value a V object.
*/
@Deprecated public BlockingReference(@NotNull final V value) {
this(new BooleanLatch(), value);
}
// /CLOVER:ON
//
// methods
//
/**
* Takes the current element if it is not null and replaces it with null. If
* the current element is null then wait until it becomes non-null.
*
* If the current thread:
*
* - has its interrupted status set on entry to this method; or
*
- is {@link java.lang.Thread#interrupt() interrupted} while waiting,
*
* then {@link java.lang.InterruptedException} is thrown and the current
* thread's interrupted status is cleared.
*
* @return the current element
* @throws java.lang.InterruptedException if the current thread is interrupted
* while waiting
*/
@NotNull public final V take() throws InterruptedException {
V result = null;
while (result == null) {
latch.await();
result = ref.getAndSet(null);
}
return result;
}
/**
* Takes the current element if it is not null and replaces it with null. If
* the current element is null then wait until it becomes non-null. The method
* will throw a {@link java.util.concurrent.TimeoutException} if the timeout
* is reached before an element becomes available.
*
* If the current thread:
*
* - has its interrupted status set on entry to this method; or
*
- is {@link java.lang.Thread#interrupt() interrupted} while waiting,
*
* then {@link java.lang.InterruptedException} is thrown and the current
* thread's interrupted status is cleared.
*
* @param time the maximum time to wait
* @param unit the time unit of the {@code timeout} argument
* @return the current element
* @throws java.lang.InterruptedException if the current thread is interrupted
* while waiting
* @throws java.util.concurrent.TimeoutException if the timeout is reached
* without another thread having called {@link #set(Object)}.
*/
@NotNull public final V take(final long time, final TimeUnit unit) throws TimeoutException, InterruptedException {
final Timeout timeout = Timeout.getNanosTimeout(time, unit);
V result = null;
while (result == null) {
timeout.await(latch);
result = ref.getAndSet(null);
}
return result;
}
/**
* Gets the current element if it is not null, if it is null then this method
* blocks and waits until it is not null. Unlike {@link #take()} it does not
* reset the current element to null.
*
* If the current thread:
*
* - has its interrupted status set on entry to this method; or
*
- is {@link java.lang.Thread#interrupt() interrupted} while waiting,
*
* then {@link java.lang.InterruptedException} is thrown and the current
* thread's interrupted status is cleared.
*
* @return the current element
* @throws java.lang.InterruptedException if the current thread is interrupted
* while waiting
*/
@NotNull public final V get() throws InterruptedException {
V result = ref.get();
while (result == null) {
latch.await();
result = ref.get();
}
return result;
}
/**
* Gets the current element if it is not null, if it is null then this method
* blocks and waits until it is not null. Unlike {@link #take()} it does not
* reset the current element to null.
*
* If the current thread:
*
* - has its interrupted status set on entry to this method; or
*
- is {@link java.lang.Thread#interrupt() interrupted} while waiting,
*
* then {@link java.lang.InterruptedException} is thrown and the current
* thread's interrupted status is cleared.
*
* @return the current element
* @throws java.util.concurrent.TimeoutException if the timeout is reached
* without another thread having called {@link #set(Object)}.
* @throws java.lang.InterruptedException if the current thread is interrupted
* while waiting
* @param time a long.
* @param unit a {@link java.util.concurrent.TimeUnit}.
*/
@NotNull public final V get(final long time, @NotNull final TimeUnit unit) throws TimeoutException, InterruptedException {
final Timeout timeout = Timeout.getNanosTimeout(time, unit);
V result = ref.get();
while (result == null) {
timeout.await(latch);
result = ref.get();
}
return result;
}
/**
* Set the value of this reference. This method is lock-free. A thread waiting
* in {@link #take()} or {@link #take(long, TimeUnit)} will be released and
* given this value.
*
* @param value the new value.
*/
public final void set(@NotNull final V value) {
requireNonNull(value, "value");
internalSet(value);
}
/**
* Whether or not the current value is null or not. If this is true and the
* next call to {@link #take()} or {@link #take(long, TimeUnit)} will not
* block.
*
* @return true if the current reference is null.
*/
public final boolean isEmpty() {
return peek() == null;
}
/**
* Return the current value whether is null or not. If this is true and the
* next call to {@link #take()} or {@link #take(long, TimeUnit)} will not
* block.
*
* @return the current reference or null if there is none.
*/
@Nullable public final V peek() {
return ref.get();
}
/**
* Clear the current reference.
*/
public final void clear() {
internalSet(null);
}
//
// private
//
/**
* Set the value
*
* @param value maybe null
*/
private final void internalSet(@Nullable final V value) {
ref.set(value);
latch.release();
}
}