понедельник, 1 июня 2015 г.

GUAVA Striped - гибкий инструмент для связывания блокировок с объектами в java

Сегодня познакомимся с полезной утилитой  Striped  в GUAVA.
Часто, при разработке многопоточного приложения, в котором разделяемые ресурсы обрабатываются множеством потоков одновременно, возникает задача корректного обеспечения доступа к  ресурсу. Например, каждый в системе ресурс идентифицируется некоторым ключом. Необходимо обеспечить действие над ним в один поток. В случае, когда ресурс один - мы создаем ReentrantLock и блокируем бы доступ другим потокам, пока один поток выполняет действие:
package ru.egrik. striped ;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author echernyshev
 * @since Aug 30, 2014
 */
public class StripedTest
{
    private Object resource;
    private Lock lock = new ReentrantLock();

    public void runAction()
    {
        lock.lock();
        try
        {
            actionWithResource(resource);
        }
        finally
        {
            lock.unlock();
        }
    }

    private void actionWithResource(Object obj)
    {
        // do something
    }
}

А если ресурсов стало много, то код будет выглядеть так:
package ru.egrik. striped;

import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author echernyshev
 * @since Aug 30, 2014
 */
public class StripedTest
{
    private Map<String, Object> resources;
    private Lock lock = new ReentrantLock();

    public void runAction(String key)
    {
        lock.lock();
        try
        {
            actionWithResource(resources.get(key));
        }
        finally
        {
            lock.unlock();
        }
    }

    private void actionWithResource(Object obj)
    {
        // do something
    }
}
Когда ресурсов становится много, использование одного lock'а не оправдано, так как это приводит к грубой синхронизации к снижению производительности системы вцелом. Возникает необходимость каждому ресурсу хранить свой lock. И если количество ресурсов заранее не зафиксировано и постоянно меняется, то при создании/удалении ресурса, необходимо создавать/удалять lock.
Эта задача изящно решена в библиотеке GUAVA утилитой  Striped .
 Striped  - утилита, позволяющая гибко связывать блокировки с ресурсами и управлять блокировками в памяти. То есть если мы хотим связать блокировку с объектом, то нам необходимо гарантировать условие: если key1.equals(key2), то блокировка связанная с key1 должна быть равна блокировке, связанной с key2.  Striped  позволяет программисту выбрать количество блокировок, которые распространяются между ключами на основе их хэш-кода. Это позволяет программисту динамически управлять компромиссом между уровнем параллелизма и занимаемой памятью, сохраняя при этом инвариантность по ключам: если key1.equals (key2), то  striped .get (key1) ==  striped .get (key2).
 Striped  содержит ряд реализаций, позволяющих использовать его для классов:
  • java.util.concurrent.locks.Lock
  • java.util.concurrent.locks.ReadWriteLock
  • java.util.concurrent.Semaphore
Кроме того, для каждой из реализаций предлагается вариант с lazy инициализацией и слабой (weak) ссылкой на lock. То есть если ни один поток не использует lock, и ни один поток не ожидает освобождения lock'а, то этот lock будет безопасно удален из памяти сборщиком мусора. Данные реализации позволят снизить потребление памяти, если в определенный момент времени будет использоваться только несколько блокировок.
Используем lazyWeakLock в нашем классе:
package ru.egrik.striped;

import java.util.Map;
import java.util.concurrent.locks.Lock;

import com.google.common.util.concurrent. Striped ;

/**
 * @author echernyshev
 * @since Aug 30, 2014
 */
public class StripedTest
{
    private Map<String, Object> resources;
    private  Striped<Lock>  striped  =  Striped.lazyWeakLock(2);

    public void runAction(String key)
    {
        Lock lock =  striped.get(key);
        lock.lock();
        try
        {
            actionWithResource(resources.get(key));
        }
        finally
        {
            lock.unlock();
        }
    }

    private void actionWithResource(Object obj)
    {
        // do something
    }
}


 Striped .lazyWeakLock(2) создаст экземпляр  Striped  с минимальным количеством требуемых локов равном двум. После того, как все потоки потеряют ссылки на lock - он безопасно удалится сборщиком мусора из памяти.

Спасибо за внимание!