Jira: потокобезопасные данные гаджета?

У меня есть некоторые данные (два HashSet и метка времени Instant), которыми я бы хотел, чтобы все запросы к моему гаджету/плагину JIRA (OpenSocial?) поможет запросам быть более производительными.

Иногда (очень редко) запрос может включать параметр, указывающий, что эти общие данные должны быть обновлены. И, конечно, в первый раз, когда это необходимо, оно заполняется. Это нормально, когда данные представляют собой устаревший ответ — они основаны на вещах, которые меняются медленно, и используются для визуализации тенденций, поэтому допустимы ошибки, связанные с ошибками.

Я предполагаю, что когда JIRA запускается (или я загружаю новую версию своего дополнения) и в течение первых нескольких минут поступает несколько запросов, мне нужно будет обрабатывать заполнение этих дорогостоящих общих данных потокобезопасным способом. . В настоящее время результаты выглядят нормально, но, насколько я понимаю, это было просто случайно.

Только один поток должен выполнять работу по заполнению. При запуске другим потокам, конечно, придется ждать, потому что они не могут пропустить вперед с пустыми руками. (Если все потоки выполняют дорогостоящую инициализацию, это создает большую ненужную нагрузку на сервер)

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

Подробнее о данных: два HashSet и отметка времени предназначены для представления согласованного моментального снимка во времени. Содержимое HashSet зависит только от значений в базе данных, а отметка времени — это только время самого последнего обновления. Никакие из этих данных не зависят от какого-либо более раннего моментального снимка во времени. И это тоже не зависит от состояния программы. Временная метка используется только для грубого ответа на вопрос «сколько лет этим данным». Каждый раз, когда данные обновляются, я ожидаю, что временная метка будет более новой, но ничего не сломается, если это неправильно. Это просто для отладки и прозрачности. Поскольку моментальный снимок не зависит от более ранних моментальных снимков или состояния программы, он может быть заключен в оболочку и помечен как volatile.

Есть ли очевидный выбор лучшего способа сделать это? Плюсы и минусы альтернатив?


person user3735178    schedule 12.06.2014    source источник


Ответы (1)


Вы захотите использовать блокировки для синхронизации доступа к разделам вашего кода, которые вам нужны, чтобы одновременно выполнялся только один поток. Существует множество ресурсов по SO и в документации Oracle Java, в которых более подробно показано, как использовать блокировки, но что-то вроде этого должно помочь.

Идея состоит в том, что вы хотите сохранить копию самого последнего сгенерированного набора результатов и всегда возвращать эту копию до тех пор, пока у вас не появится новый доступный набор данных.

import java.util.concurrent.locks.ReentrantLock;

public class MyClass
{
    private volatile MyObject completedResults;
    private final ReentrantLock resultsLock;
    private final ReentrantLock refreshLock;

    public MyClass()
    {
        // This must be a singleton class (such as a servlet) for this to work, since every
        // thread needs to be accessing the same lock.

        resultsLock = new ReentrantLock();
        refreshLock = new ReentrantLock();
    }

    public MyObject myMethodToRequestResults(boolean refresh)
    {
        MyObject resultsToReturn;

        // Serialize access to get the most-recently completed set of results; if none exists,
        // we need to generate it and all requesting threads need to wait.

        resultsLock.lock();

        try
        {
            if (completedResults == null)
            {
                completedResults = generateResults();
                refresh = false; // we just generated it, so no point in redoing it below
            }

            resultsToReturn = completedResults;
        }
        finally
        {
            resultsLock.unlock();
        }

        if (refresh)
        {
            // If someone else is regenerating, we just return the old data and tell the caller that.

            if (!refreshLock.tryLock())
            {
                // create a copy of the results to return, since we're about to modify it on the next line
                // and we don't want to change the (shared) original!

                resultsToReturn = new MyObject(resultsToReturn);  
                resultsToReturn.setSomeoneElseIsRegeneratingTheStuffRightNow(true);
            }
            else
            {
                try
                {
                    completedResults = generateResults();
                    resultsToReturn = completedResults;
                }
                finally
                {
                    refreshLock.unlock();
                }
            }
        }

        return resultsToReturn;
    }
}
person Scott Dudley    schedule 13.06.2014
comment
Как насчет синхронизированного метода, который принимает в качестве входных данных перечисление, представляющее [1] Я просто хочу, чтобы вы получили сейчас [2] то же, что и 1, но также запрашиваю обновление и [3] У меня должен быть правильный ответ, и я готов сделать работа для него. Метод synchronized немедленно возвращается с копией данных, связанных с тем, является ли вызывающий объект проигравшим потоком, которому необходимо выполнить задание обновления. Затем вызывающая сторона принимает этот результат и действует соответствующим образом — либо используя данные, либо платя за обновление и установку изменчивого набора данных в сервлете с последними результатами обновления. Следовательно, нет состояния гонки? - person user3735178; 13.06.2014
comment
Первоначальные абоненты — одному говорят, что он проигравший, поэтому они начинают вычислять данные. Другим предоставляется нулевая копия, и они должны понимать, что им нужно начать опрос для ненулевого результата. - person user3735178; 13.06.2014
comment
Я отредактировал ответ, чтобы использовать tryLock() вместо isLocked(), что соответствует условиям исходного вопроса, а также удаляет ранее прокомментированное состояние гонки. Новое поведение с тремя вариантами, которое вы хотите в комментарии выше, добавляет больше сложности, поэтому вам, вероятно, придется добавить явный обратный вызов lock(), если первоначальный tryLock() терпит неудачу, и расположить условные операторы так, чтобы после этого они выполнялись правильно. - person Scott Dudley; 13.06.2014