Одновременно диспетчер данных с минимальными синхронизации


Так у меня есть своя библиотека, чтобы организовать дополнительные критические секции на GitHub. Он основан на классе ResourceDataStorage, который отвечает за создание, хранение и очистка записей ресурсов. Я постарался минимизировать блоков синхронизации попаданий и я использовал функцию перепев, который отличается от ConcurrentHashMap ворошить, чтобы сохранить здоровые частности:

package zur13.checkpoint.resource.storage;

import java.util.concurrent.ConcurrentHashMap;

import zur13.checkpoint.resource.AResourceData;
import zur13.checkpoint.resource.ResourceDataFactory;

/**
 * Provides thread safe operations to store, create and release Resource Data objects.
 * Has critical sections at get() and release() operations.
 * Synchronously clears internal records for resource if no references left on release().
 *
 */
public class ResourceDataStorage {
    ConcurrentHashMap<Object, AResourceData> dataBuckets[];
    ResourceDataFactory adf;

    @SuppressWarnings("unchecked")
    public ResourceDataStorage(ResourceDataFactory adf, int concurrencyLevel) {
        super();
        this.adf = adf;
        dataBuckets = new ConcurrentHashMap[concurrencyLevel];
        for (int i = 0; i < dataBuckets.length; i++) {
            dataBuckets[i] = new ConcurrentHashMap<Object, AResourceData>();
        }
    }

    /**
     * Retrieve ResourceData instance for the given resource.
     * Create new instance of the ResourceData if no instance stored for the given resource.
     * 
     * Release ResourceData after the passes it supplied is returned or you have done working with it.
     * 
     * @return
     */
    public AResourceData get(Object resourceId) {
        AResourceData ad = null;
        AResourceData adPrev = null;

        int bucketIdx = spread(resourceId.hashCode()) % dataBuckets.length;
        ConcurrentHashMap<Object, AResourceData> resourcesDataBucket = dataBuckets[bucketIdx];

        ad = resourcesDataBucket.get(resourceId);

        if ( ad == null || ad.getRefCounter().getAndIncrement() <= 0 ) {
            adPrev = ad;
            synchronized (resourcesDataBucket) {
                ad = resourcesDataBucket.get(resourceId);
                if ( ad == null ) {
                    ad = adf.getResourceData(resourceId); // default refCounter == 1
                    resourcesDataBucket.put(resourceId, ad);
                } else if ( ad != adPrev ) {
                    // ResourceData was recreated and put to Hash Map after refCounter.getAndIncrement()
                    // but before synchronized() block
                    ad.getRefCounter().getAndIncrement();
                }
            }
        }

        return ad;
    }

    /**
     * Release ResourceAccessController instance and clear it from the storage if no references left.
     *
     * @param resourceId
     */
    public void release(Object resourceId) {
        int bucketIdx = spread(resourceId.hashCode()) % dataBuckets.length;
        ConcurrentHashMap<Object, AResourceData> resourcesDataBucket = dataBuckets[bucketIdx];

        AResourceData ad = resourcesDataBucket.get(resourceId);

        if ( ad.getRefCounter().decrementAndGet() <= 0 ) {
            synchronized (resourcesDataBucket) {
                if ( ad.getRefCounter().get() <= 0 ) {
                    resourcesDataBucket.remove(ad.getResourceId());
                }
            }
        }
    }

    /**
     * Spread hash to minimize collisions inside ConcurrentHashMaps
     * 
     * @param h
     * @return
     */
    static final int spread(int h) {
        return (h ^ (h >> 8));
    }
}

И вот класс AResourceData я урезать некоторые вещи, которые не важны для этого вопроса

package zur13.checkpoint.resource;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import zur13.checkpoint.ACheckpoint;
import zur13.checkpoint.Pass;

/**
 * Stores active passes data for a single resource.
 */
public abstract class AResourceData {
    protected Object resourceId;
    protected AtomicLong refCounter = new AtomicLong(1);

    public AResourceData() {
        super();
    }

    public Object getResourceId() {
        return this.resourceId;
    }

    public AtomicLong getRefCounter() {
        return this.refCounter;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((this.resourceId == null) ? 0 : this.resourceId.hashCode());
        return result;
    }

    /**
     * Allows comparison with other resourceId.
     */
    @Override
    public boolean equals(Object obj) {
        if ( this == obj )
            return true;
        if ( obj == null )
            return false;

        if ( obj instanceof AResourceData ) {
            AResourceData other = (AResourceData) obj;
            if ( this.resourceId == null ) {
                if ( other.resourceId != null )
                    return false;
            } else if ( !this.resourceId.equals(other.resourceId) )
                return false;
        } else {
            // suggest that obj is resourceId
            if ( this.resourceId == null ) {
                if ( obj != null )
                    return false;
            } else if ( !this.resourceId.equals(obj) )
                return false;
        }
        return true;
    }
}

И ResourceDataFactory довольно прост, вот только нужен способ от нее:

public AResourceData getResourceData(Object resourceId) {
        return new ResourceData(resourceId, maxActivePassesPerResource, fair);
}


113
3
задан 9 апреля 2018 в 08:04 Источник Поделиться
Комментарии
1 ответ

Марк переменных в качестве окончательной, когда они не должны меняться


public class ResourceDataStorage {
ConcurrentHashMap<Object, AResourceData> dataBuckets[];
ResourceDataFactory adf;

public abstract class AResourceData {
protected Object resourceId;
protected AtomicLong refCounter = new AtomicLong(1);


На данный момент, эти поля могут быть изменены классы в том же пакете, потенциально нарушая потокобезопасным контракта

final ConcurrentHashMap<Object, AResourceData> dataBuckets[];
final ResourceDataFactory adf;

protected final Object resourceId;
protected final AtomicLong refCounter = new AtomicLong(1);

Код перерывы после более чем 9,223,372,036,854,775,807 обращается к get метод ResourceDataStorage без выхода


    if ( ad == null || ad.getRefCounter().getAndIncrement() <= 0 ) {

В то время как технически это ошибка, это вряд ли получится срабатывает в краткосрочной перспективе, однако это может срабатывать в долгосрочной перспективе для длительных применений.

НПЭ в свой способ выхода


   AResourceData ad = resourcesDataBucket.get(resourceId);

if ( ad.getRefCounter().decrementAndGet() <= 0 ) {


Что, если этот объект не просили? Он будет бросать НПЭ

Неэффективность между синхронизации & ConcurrentHashMaps те

Внутри ConcurrentHashMaps использует ту же систему ведро объект, как вы эффективно разработана, используя обе эти системы в то же время, вы в основном тратите время, делая замок, синхронизация делая несколько ведер.

Я предлагаю просто полагаться на ведра, хранящиеся внутри ConcurrentHashMaps и опуская свои ведра системы, и просто проходя мимо concurrencyLevel напрямую к карте-конструктор.

Добавить значение null проверяет в конструкторах


public ResourceDataStorage(ResourceDataFactory adf, int concurrencyLevel) {
super();
this.adf = adf;

Добавив проверку на null внутри конструкторов, вы получите сюрприз, когда делает объект, а не 10 методов позже, когда вы на самом деле использовать функцию

 public ResourceDataStorage(ResourceDataFactory adf, int concurrencyLevel) {
super();
this.adf = Object.requireNonNull(adf, "adf");

Исправить javadoc предупреждения

Внутри у javadoc, есть несколько предупреждений


/**
* Retrieve ResourceData instance for the given resource.
* Create new instance of the ResourceData if no instance stored for the given resource.
*
* Release ResourceData after the passes it supplied is returned or you have done working with it.
*
* @return
*/
public AResourceData get(Object resourceId) {


  • Отсутствует @param для resourceId


/**
* Allows comparison with other resourceId.
*/
@Override
public boolean equals(Object obj) {


  • Отсутствует @param для obj

  • Отсутствует @return

Договор нарушение .equals & .hashcode


/**
* Allows comparison with other resourceId.
*/
@Override
public boolean equals(Object obj) {
if ( this == obj )
return true;
if ( obj == null )
return false;

if ( obj instanceof AResourceData ) {
AResourceData other = (AResourceData) obj;
if ( this.resourceId == null ) {
if ( other.resourceId != null )
return false;
} else if ( !this.resourceId.equals(other.resourceId) )
return false;
} else {
// suggest that obj is resourceId
if ( this.resourceId == null ) {
if ( obj != null )
return false;
} else if ( !this.resourceId.equals(obj) )
return false;
}
return true;
}


Согласно документации .equalsона должна быть:



  • Это симметрично: для любых ненулевых ссылочных значений x и Y, то x.метод Equals(y) должен возвращать true, если и только если y.метод Equals(X) возвращает true.


Ваш код не выполняется так:

AResourceData data = new AResourceData();
String key = "hi";
data.resourceId = key;
key.equals(data); // false
data.equals(key); // true

Согласно документации hashcode:


Если два объекта равны согласно Equals(Object) метод, затем вызывая метод hashCode на каждом из двух объектов должен привести к тому же самому целочисленному результату.

AResourceData data = new AResourceData();
String key = "hi";
data.resourceId = key;
data.equals(key); // true
data.hashCode() == key.hashCode(); //false

От разрыва контракта из существующих методов, вы, в принципе, ввести под документально мире, и это может дать нежелательные побочные эффекты и потенциально будущих ошибок.

Неиспользуемые импорты


import java.util.concurrent.TimeUnit;
import zur13.checkpoint.ACheckpoint;
import zur13.checkpoint.Pass;

Весь импорт на верхнем нигде не используются внутри разместил код

Добавить дженерики в код

На данный момент, код принимает только Object в качестве основных видах, добавляя дженериков в сам код, человек может повторно использовать его более аккуратно только прием домена конкретных типов, это особенно важно для людей, пишущих реализации на ResourceDataFactory.getResourceData способ

1
ответ дан 9 апреля 2018 в 10:04 Источник Поделиться