All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ru.fix.dynamic.property.api.AtomicProperty Maven / Gradle / Ivy

There is a newer version: 2.0.8
Show newest version
package ru.fix.dynamic.property.api;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.fix.stdlib.reference.CleanableWeakReference;
import ru.fix.stdlib.reference.ReferenceCleaner;

import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Provides simple implementation for DynamicProperty. 
* Allows to manually provide DynamicProperty in tests.
* Be aware that this class does not synchronize value update and listener invocations.
* Listeners will be invoked during {@link #set(Object)} in the same thread.
*
*
{@code
 * MyService(DynamicProperty property){
 *      property.addAndCallListener(value -> initialize(value))
 * }
 * property = AtomicProperty(1)
 * MyService myService = MyService(property)
 * result = myService.doWork()
 * assertThat(result, ...)
 * }
*

* Be aware that listeners invoked in same thread, that calls {@link #set(Object)}
* This class does not provide any synchronization except holding volatile variable in order to stay lightweight
* Here is an example that leads to race condition: * *

{@code
 * MyService(DynamicProperty property){
 *      property.addAndCallListener(value -> initialize(value))
 * }
 * //DO NOT DO THAT
 * property = AtomicProperty(1)
 * new Thread{ property.set(2) }.start()
 * MyService myService = MyService(property)
 * //Problem 1. It is not clear what value MyService will see 1 or 2 or both.
 * //Problem 2. MyService can end up with a stale value of 1.
 * //Problem 3. MyService method initialize could be invoked twice concurrently.
 * //Problem 4. MyService method initialize could be invoked twice and see wrong order of changes:
 * // at first it will see 2 and then 1.
 * }
*

* In order to prevent all four problems user of AtomicProperty should organize proper thread safe usages of the property. * * @author Kamil Asfandiyarov */ public class AtomicProperty implements DynamicProperty { private static final Logger log = LoggerFactory.getLogger(AtomicProperty.class); private final Object changeValueAndAddListenerLock = new Object(); private final AtomicReference valueHolder = new AtomicReference<>(); private final Set>> subscriptions = Collections.newSetFromMap(new ConcurrentHashMap<>()); private String name = null; private final ReferenceCleaner referenceCleaner = ReferenceCleaner.getInstance(); public AtomicProperty() { } public AtomicProperty(T value) { this.valueHolder.set(value); } public void setName(String name) { this.name = name; } /** * @param newValue * @return old value */ public T set(T newValue) { T oldValue; synchronized (changeValueAndAddListenerLock) { oldValue = valueHolder.getAndSet(newValue); subscriptions.forEach(ref -> { try { var subscription = ref.get(); if (subscription != null) { subscription.listener.onPropertyChanged(oldValue, newValue); } } catch (Exception exc) { log.error("Failed to notify listener on property change." + " Property name {}, old value {}, new value {}.", name, oldValue, newValue, exc); } }); } subscriptions.removeIf(ref -> ref.get() == null); return oldValue; } @Override public T get() { return valueHolder.get(); } private static class Subscription implements PropertySubscription { private final AtomicProperty sourceProperty; private PropertyListener listener; private CleanableWeakReference> attachedSubscriptionReference; Subscription(AtomicProperty sourceProperty) { this.sourceProperty = sourceProperty; } @Override public T get() { return sourceProperty.get(); } @Override public PropertySubscription setAndCallListener(@Nonnull PropertyListener listener) { this.listener = listener; this.sourceProperty.attachSubscriptionAndCallListener(this); return this; } @Override public void close() { sourceProperty.detachSubscription(this); } } @Override @Nonnull public PropertySubscription createSubscription() { return new Subscription<>(this); } private void detachSubscription(Subscription subscription) { if(subscription.attachedSubscriptionReference != null) { subscriptions.remove(subscription.attachedSubscriptionReference); subscription.attachedSubscriptionReference = null; } } private void attachSubscriptionAndCallListener(Subscription subscription){ detachSubscription(subscription); synchronized (changeValueAndAddListenerLock) { if(subscription.attachedSubscriptionReference != null){ subscriptions.remove(subscription.attachedSubscriptionReference); subscription.attachedSubscriptionReference.cancelCleaningOrder(); subscription.attachedSubscriptionReference = null; } CleanableWeakReference> cleanableWeakReference = referenceCleaner.register( subscription, null, (reference, meta) -> subscriptions.remove(reference) ); subscription.attachedSubscriptionReference = cleanableWeakReference; subscriptions.add(cleanableWeakReference); subscription.listener.onPropertyChanged(null, valueHolder.get()); } } @Override public void close() { subscriptions.clear(); } @Override public String toString() { return "AtomicProperty(" + valueHolder.get() + ")"; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy