
com.aitusoftware.flute.exchanger.MultiWriterTimeBasedValueExchanger Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2016 Aitu Software Limited.
*
* 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 com.aitusoftware.flute.exchanger;
import com.aitusoftware.flute.collection.LockFreeCopyOnWriteArray;
import com.aitusoftware.flute.compatibility.BiConsumer;
import com.aitusoftware.flute.compatibility.LongSupplier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* Utility class to swap two instances of a type between multiple writers and a single reader thread.
*
* @param instance type
*/
public final class MultiWriterTimeBasedValueExchanger implements Exchanger
{
private final AtomicReference container;
private final LongSupplier millisecondClock;
private final long publicationIntervalMillis;
private final BiConsumer publishedValueConsumer;
private final TimeWindow timeWindow = new TimeWindow();
private final AtomicWriterEpochTracker writerEpochTracker = new AtomicWriterEpochTracker();
private final LockFreeCopyOnWriteArray epochTrackers =
new LockFreeCopyOnWriteArray();
private static final ThreadLocal EPOCH_TRACKER_THREAD_LOCAL =
new ThreadLocal();
private T next;
private long lastPublicationTimestamp;
/**
* Constructor
*
* @param initial initial value
* @param next subsequent value
* @param publishedValueConsumer consumer for swapped-out instance
* @param millisecondClock provider for epoch milliseconds
* @param publicationInterval interval between attempts to swap and publish instances
* @param timeUnit interval unit
*/
public MultiWriterTimeBasedValueExchanger(final T initial,
final T next,
final BiConsumer publishedValueConsumer,
final LongSupplier millisecondClock,
final long publicationInterval,
final TimeUnit timeUnit)
{
this.container = new AtomicReference(initial);
this.next = next;
this.millisecondClock = millisecondClock;
this.publishedValueConsumer = publishedValueConsumer;
this.publicationIntervalMillis = timeUnit.toMillis(publicationInterval);
this.lastPublicationTimestamp = millisecondClock.getAsLong();
}
/**
* Acquire the 'current' instance for writing.
*
* MUST be followed by a call to release from the mutating thread.
*
* @return the 'current' instance
*/
public T acquire()
{
if(EPOCH_TRACKER_THREAD_LOCAL.get() == null)
{
final AtomicWriterEpochTracker epochTracker = new AtomicWriterEpochTracker();
EPOCH_TRACKER_THREAD_LOCAL.set(new EpochState(epochTracker));
epochTrackers.add(epochTracker);
}
EPOCH_TRACKER_THREAD_LOCAL.get().enterWriteSection();
return container.get();
}
/**
* Notification from the mutating thread that the current write operation is complete.
*/
public void release()
{
EPOCH_TRACKER_THREAD_LOCAL.get().exitWriteSection();
}
/**
* Called by the reader thread. If publication interval has expired, will attempt
* to swap the 'current' instance, and publish the most-recently-updated value.
*/
public void poll()
{
final long currentMillis = millisecondClock.getAsLong();
if(currentMillis >= lastPublicationTimestamp + publicationIntervalMillis)
{
final T current = container.get();
container.compareAndSet(current, next);
writerEpochTracker.awaitPendingWrite(1L);
timeWindow.set(lastPublicationTimestamp, currentMillis);
publishedValueConsumer.accept(current, timeWindow);
next = current;
lastPublicationTimestamp = currentMillis;
}
}
private static final class EpochState
{
private final AtomicWriterEpochTracker epochTracker;
private long currentEpoch;
private EpochState(final AtomicWriterEpochTracker epochTracker)
{
this.epochTracker = epochTracker;
}
private void enterWriteSection()
{
currentEpoch = epochTracker.enterWriteSection();
}
private void exitWriteSection()
{
epochTracker.exitWriteSection(currentEpoch);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy