3

我有一堂课建议翻译实用程序。翻译本身应每 30 分钟重新加载一次。我为此使用 Spring Timer 支持。基本上,我的课看起来像:

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

loadTranslations() 运行时间可能很长,因此在运行时旧的翻译仍然可用。这是通过在本地地图中加载翻译并在加载所有翻译时更改参考来完成的。

我的问题是:我如何确保当一个线程已经加载翻译时,第二个线程也尝试运行,它检测到并立即返回,而不开始第二次更新。

同步方法只会将负载排队...我仍在使用 Java 1.4,所以没有 java.util.concurrent。

谢谢你的帮助 !

4

4 回答 4

3

使用某种形式的锁定机制仅在任务尚未进行时才执行该任务。获取锁定令牌必须是一个一步的过程。看:

/**
 * @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();
        }
    }

}
于 2008-09-30T11:06:16.383 回答
1

我来自 .net 背景(根本没有 Java 经验),但是您可以尝试使用某种简单的静态标志,在方法开始时检查它是否已经运行。然后,您需要做的就是确保对该标志的任何读/写都是同步的。因此,在开始时检查标志,如果未设置,则设置它,如果已设置,则返回。如果未设置,则运行该方法的其余部分,并在完成后取消设置。只需确保将代码放在 try/finally 中,并将标志 iunsetting 放在 finally 中,这样在出现错误时它总是会被取消设置。非常简化,但可能就是您所需要的。

编辑:这实际上可能比同步方法更好。因为您真的需要在翻译完成之前立即进行新的翻译吗?如果必须等待一段时间,您可能不想将线程锁定太久。

于 2008-09-30T07:57:16.633 回答
0

在加载线程上保留一个句柄以查看它是否正在运行?

或者您不能只使用同步标志来指示加载是否正在进行?

于 2008-09-30T07:55:50.287 回答
0

这实际上与以经典方式完成时管理单例构造(喘气!)所需的代码相同:

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

内测与外测相同。外层测试是为了让我们不要例行进入同步块,内层测试是确认自从我们上次进行测试后情况没有改变(线程可能在进入同步块之前就被抢占了)。

在你的情况下:

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

更新:这种构造单例的方式在您的 JDK1.4 下无法可靠地工作。有关说明,请参见此处。但是,我认为您在这种情况下会没事的。

于 2008-09-30T08:17:29.673 回答