Синхронизация Java 1.4: разрешить запуск только одного экземпляра метода (без блокировки)? - PullRequest
3 голосов
/ 30 сентября 2008

У меня есть класс, предлагающий переводы утилит. Сами переводы должны обновляться каждые 30 минут. Я использую поддержку Spring Timer для этого. В основном мой класс выглядит так:

public interface Translator {
    public void loadTranslations();
    public String getTranslation(String key);
}

loadTranslations () может выполняться довольно долго, поэтому во время работы старые переводы все еще доступны. Это делается путем загрузки переводов на локальную карту и просто путем изменения ссылки, когда все переводы загружены.

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

Синхронизированный метод будет только ставить в очередь загрузки ... Я все еще на Java 1.4, поэтому нет java.util.concurrent.

Спасибо за вашу помощь!

Ответы [ 4 ]

3 голосов
/ 30 сентября 2008

Используйте какой-либо механизм блокировки для выполнения задачи, только если она еще не выполняется. Получение токена блокировки должно быть одношаговым процессом. См:

/**
 * @author McDowell
 */
public abstract class NonconcurrentTask implements Runnable {

    private boolean token = true;

    private synchronized boolean acquire() {
        boolean ret = token;
        token = false;
        return ret;
    }

    private synchronized void release() {
        token = true;
    }

    public final void run() {
        if (acquire()) {
            try {
                doTask();
            } finally {
                release();
            }
        }
    }

    protected abstract void doTask();

}

Тестовый код, который выдаст исключение, если задача выполняется одновременно:

public class Test {

    public static void main(String[] args) {
        final NonconcurrentTask shared = new NonconcurrentTask() {
            private boolean working = false;

            protected void doTask() {
                System.out.println("Working: "
                        + Thread.currentThread().getName());
                if (working) {
                    throw new IllegalStateException();
                }
                working = true;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                if (!working) {
                    throw new IllegalStateException();
                }
                working = false;
            }
        };

        Runnable taskWrapper = new Runnable() {
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    shared.run();
                }
            }
        };
        for (int i = 0; i < 100; i++) {
            new Thread(taskWrapper).start();
        }
    }

}
1 голос
/ 30 сентября 2008

Я из .net фона (вообще без опыта Java), но вы можете попробовать какой-нибудь простой статический флаг, который проверяет в начале метода, работает ли его алгоритм. Тогда все, что вам нужно сделать, это убедиться, что все операции чтения / записи этого флага синхронизированы. Поэтому в начале проверьте флаг, если он не установлен, установите его, если он установлен, верните. Если он не установлен, запустите оставшуюся часть метода и после его завершения сбросьте его. Просто убедитесь, что код вставлен в try / finally и флаг iunsetting установлен в finally, чтобы он всегда сбрасывался в случае ошибки. Очень упрощенный, но может быть все, что вам нужно.

Редактировать: это, вероятно, работает лучше, чем синхронизация метода. Потому что вам действительно нужен новый перевод сразу после того, как он закончится? И, возможно, вы не захотите слишком долго блокировать поток, если ему придется подождать некоторое время.

0 голосов
/ 30 сентября 2008

Это фактически идентично коду, необходимому для управления конструкцией Singleton (задыхаясь!), Когда это делается классическим способом:

if (instance == null) {
  synchronized {
    if (instance == null) {
       instance = new SomeClass();
    }
  }
}

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

В вашем случае:

if (translationsNeedLoading()) {
  synchronized {
    if (translationsNeedLoading()) {
       loadTranslations();
    }
  }
}

ОБНОВЛЕНИЕ: этот способ построения синглтона не будет надежно работать под вашим JDK1.4. Для объяснения см. Здесь . Однако я думаю, что вы с вами все будет в порядке в этом сценарии.

0 голосов
/ 30 сентября 2008

Держите ручку в потоке загрузки, чтобы увидеть, работает ли он?

Или вы не можете просто использовать синхронизированный флаг, чтобы указать, выполняется ли загрузка?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...