代码之家  ›  专栏  ›  技术社区  ›  Guillaume

Java 1.4同步:只允许一个方法实例运行(非阻塞)?

  •  3
  • Guillaume  · 技术社区  · 16 年前

    我有一个班提议翻译实用程序。翻译本身应该每30分钟重新加载一次。我用弹簧定时器来支持它。基本上,我的课看起来像:

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

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

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

    同步方法将只对加载进行排队…我仍然在Java 1.4上,所以没有JavaUTI.并发。

    谢谢你的帮助!

    4 回复  |  直到 12 年前
        1
  •  3
  •   McDowell rahul gupta    16 年前

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

    /**
     * @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();
            }
        }
    
    }
    
        2
  •  1
  •   mattlant    16 年前

    我来自.NET后台(根本没有Java经验),但是您可以尝试一种简单的静态标记,在该方法的开头检查它的ALADRY运行。然后,您需要做的就是确保该标志的任何读/写都是同步的。所以在开始的时候,检查这个标志,如果它没有被设置,设置它,如果它被设置,返回。如果没有设置,则运行该方法的其余部分,完成后取消设置。只需确保将代码放入try/finally中,并在finally中放入标记iusetting,以便在出现错误时始终取消设置。非常简单,但可能是你所需要的。

    编辑:这实际上可能比同步方法更好。因为你真的需要一个新的翻译吗 立即 在它结束之前?如果线程必须等待一段时间,那么您可能不想将其锁定太长时间。

        3
  •  0
  •   cagcowboy    16 年前

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

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

        4
  •  0
  •   Ewan Makepeace    16 年前

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

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

    内部测试与外部测试相同。外部测试是为了我们不经常进入同步块,内部测试是为了确认自上次进行测试以来情况没有改变(线程可能在进入同步之前被抢占)。

    在你的情况下:

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

    更新:这种构造单例的方法在JDK1.4下不能可靠地工作。为了解释 see here . 不过,我认为你在这种情况下是可以的。