以下代码线程是否安全?

时间:2014-04-24 15:32:00

标签: java multithreading thread-safety

我认为我已经实现了Double-checked锁定模式,但不确定它是否安全或是否按预期工作。实现相同的任何其他逻辑都会非常有用。

public class OnProperties {

    private static String dfltPropertyFile = "on.properties";
    private static long refreshSecs = 120L;
    private static Properties props;
    private static long lastReadTimestamp = 0;


    public static String getProperty(String propertyName, String dfltValue) {
        long currentTimestamp = System.currentTimeMillis() / 1000L;

        if (props == null
                || (refreshSecs > 0 && (currentTimestamp - lastReadTimestamp) > refreshSecs)) {
            synchronized (props) {
                if (props == null
                        || (refreshSecs > 0 && (currentTimestamp - lastReadTimestamp) > refreshSecs)) {
                    lastReadTimestamp = currentTimestamp;
                    try {
                        loadProperties(dfltPropertyFile);
                        refreshSecs = getProperty("on.properties.refresh", 120L);
                        if (refreshSecs < 0L) {
                            refreshSecs = 0L;
                        }
                    } catch (Exception e) {
                        refreshSecs = 600L;
                    }
                }
            }
        }

        if (props == null) {
            return dfltValue;
        }

        String propertyValue = props.getProperty(propertyName, dfltValue);

        return propertyValue;
    }

    public static boolean getProperty(String propertyName, boolean dfltValue) {
        boolean value = dfltValue;

        String strValue = getProperty(propertyName, (String) null);
        if (strValue != null) {
            try {
                value = Boolean.parseBoolean(strValue);
            } catch (NumberFormatException e) {
                // just keep the default
            }

        }
        return value;
    }

    private static void loadProperties(String p_propertiesFile)
            throws java.io.IOException, java.io.FileNotFoundException {
        InputStream fileStream = new FileInputStream(p_propertiesFile);
        props = new Properties();
        props.load(fileStream);
        fileStream.close();
    }
}

通常,多个运行的线程经常访问“getProperty”方法,如下所示:

extDebug = OnProperties.getProperty("on.extdebug", false); 

5 个答案:

答案 0 :(得分:1)

原子值保证始终将完整的最新值返回给所有线程。在这种情况下,这可以防止许多多线程问题。仍然需要一点同步,但可以将其限制到最小。请参阅下面的实施:

import java.io.File;
import java.io.FileInputStream;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

public class OnProperties {


private static int refreshIntervalDefaultSecs;
private static int refreshIntervalOnErrorSecs;

static {
    setRefreshInterval(120);
}

private static final AtomicReference<Properties> propsRef = new AtomicReference<Properties>(new Properties());
private static final AtomicLong nextPropsLoad = new AtomicLong(0L);
private static final Object loadLock = new Object();

private static String dfltPropertyFile  = "on.properties";

public static String getProperty(String key, String defaultValue) {

    String value = getProperty(key);
    if (value == null) {
        value = defaultValue;
    }
    return value;
}

private static String getProperty(String key) {

    reloadWhenNeeded();
    return propsRef.get().getProperty(key);
}

private static void reloadWhenNeeded() {

    long now = System.currentTimeMillis();
    if (now > nextPropsLoad.get()) {
        boolean reload = false;
        synchronized(loadLock) {
            if (now > nextPropsLoad.get()) {
                // need loadLock because there is time between previous get()
                // and next set()
                updateNextPropsLoad(now, refreshIntervalDefaultSecs);
                reload = true;
            }
        }
        if (reload) {
            reloadProps(now);
        }
    }
}

private static void updateNextPropsLoad(long now, int nextRefreshSecs) {
    nextPropsLoad.set(now + nextRefreshSecs * 1000);
}

private static void reloadProps(long now) {

    Properties p = new Properties();
    FileInputStream in = null;

    System.out.println("Reloading from " + new File(dfltPropertyFile).getAbsolutePath());

    try { 
        p.load(in = new FileInputStream(new File(dfltPropertyFile)));
        propsRef.set(p);
        setRefreshInterval(getProperty("on.properties.refresh", 120));
        updateNextPropsLoad(now, refreshIntervalDefaultSecs);
    } catch (Exception e) {
        updateNextPropsLoad(now, refreshIntervalOnErrorSecs);
    } finally {
        try { if (in != null) in.close(); } catch (Exception e) {
            updateNextPropsLoad(now, refreshIntervalOnErrorSecs);
        }
    }
}

private static void setRefreshInterval(int refreshSecs) {

    if (refreshSecs < 1) {
        refreshSecs = 120;
    }
    refreshIntervalDefaultSecs = refreshSecs;
    refreshIntervalOnErrorSecs = 5 * refreshSecs;
}

public static boolean getProperty(String key, boolean defaultValue) {

    boolean value = defaultValue;
    String svalue = getProperty(key);
    if (svalue != null) {
        try {
            value = Boolean.valueOf(svalue);
        } catch (Exception ignored) {}
    }
    return value;
}

public static int getProperty(String key, int defaultValue) {

    int value = defaultValue;
    String svalue = getProperty(key);
    if (svalue != null) {
        try {
            value = Integer.valueOf(svalue);
        } catch (Exception ignored) {}
    }
    return value;
}

public static void main(String[] args) {

    System.out.println("Refresh value from file: " + getProperty("on.properties.refresh", 120));
    System.out.println("No reload " + getProperty("does.not.exist", true));
    System.out.println("Next reload after " + ((nextPropsLoad.get() - System.currentTimeMillis()) / 1000) + " seconds.");
}

}

实现的一个缺点是,当选择从文件重新加载属性时,一个线程将变慢。更好的方法是创建一个'监视程序'线程/计划任务,检查每个(例如)五秒钟,如果属性文件具有更改的修改日期,然后触发重新加载(在这种情况下,属性的AtomicReference仍然会出现派上用场)。
还要记住,存在逻辑线程问题:如果属性值是相互关联的(即,如果另一个值也被更新,则一个值只是正确的),重新加载可能会呈现具有旧值和新值的线程,混合。唯一的方法是在使用属性的相关值的方法中保持对一组属性的引用(在这种情况下,使用静态方法和变量这样的类不方便)。

答案 1 :(得分:0)

这是不安全的,因为你有多个变量以非线程安全的方式读取(即访问不同步且它们不易变)。

看起来工作流程主要是通过一些写入来读取。我建议使用ReentrantReadWriteLock来同步访问权限。

答案 2 :(得分:0)

要使双重检查锁定正常工作,您必须做两件事:

  • private static Properties props必须声明为volatile;
  • 如前所述,如果props为null,synchronized(props)将无效 - 您需要声明一个特殊的锁定对象字段:

private static final Object propsLockObject = new Object();
...
synchronized(propsLockObject) { 
...

P.S。除非声明为volatile,否则lastReadTimestamp也不会起作用。虽然这不再是双重检查锁定。

答案 3 :(得分:0)

  1. 要重新加载属性,您不需要重新初始化props变量。在声明语句本身期间初始化属性。这将解决与null同步的问题。
  2. 删除loadProperties块中的初始化代码。
  3. 删除同步块外部和内部的prop == null检查。
  4. 完成后,您的代码将按照您希望的方式运行。

    public class OnProperties {

    private static String dfltPropertyFile = "on.properties"; 
    private static long refreshSecs = 120L; 
    private static Properties props = new Properties();
    private static long lastReadTimestamp = 0;
    public static String getProperty(String propertyName, String dfltValue) { 
    
    long currentTimestamp = System.currentTimeMillis() / 1000L;
    
      if (refreshSecs > 0 && (currentTimestamp - lastReadTimestamp) > refreshSecs) {
          synchronized (props) {
              if (refreshSecs > 0 && (currentTimestamp - lastReadTimestamp) > refreshSecs) {
                  lastReadTimestamp = currentTimestamp;
                  try {
                      loadProperties(dfltPropertyFile);
                      refreshSecs = getProperty("on.properties.refresh", 120L);
                      if (refreshSecs < 0L) {
                          refreshSecs = 0L;
                      }
                  } catch (Exception e) {
                      refreshSecs = 600L;
                  }
              }
          }
      }
    
      String propertyValue = props.getProperty(propertyName, dfltValue);
    
      return propertyValue;
    
    }
    
    public static boolean getProperty(String propertyName, boolean dfltValue) { boolean value = dfltValue;
    
      String strValue = getProperty(propertyName, (String) null);
      if (strValue != null) {
          try {
              value = Boolean.parseBoolean(strValue);
          } catch (NumberFormatException e) {
              // just keep the default
          }
    
      }
      return value;
    
    }
    
    private static void loadProperties(String p_propertiesFile) throws java.io.IOException, java.io.FileNotFoundException { InputStream fileStream = new FileInputStream(p_propertiesFile); props.load(fileStream); fileStream.close(); } }
    

答案 4 :(得分:0)

请接受双重检查的锁定习惯用法被破坏且不起作用(即不能正确同步)。即使你使用volatile(在正确的地方)使其工作,它也太复杂了。

所以我的建议是:简单地同步一切。然后尝试衡量。如果您发现OnProperties是瓶颈,请考虑更强大/更聪明的同步技术,并在必要时返回:

public class OnProperties {
    /* some private fields here */

    public static synchronized String getProperty(String propertyName, String dfltValue) {
        reloadPropertiesIfNecessary();

        return props.getProperty(propertyName, dfltValue);
    }

    /* other public methods using getProperty come here */

    private static void reloadPropertiesIfNecessary() {
        // check timestamp etc.
        if (/* check timestamp etc. */) {
            loadProperties(dfltPropertyFile);
            // update timestamp etc.
        }
    }

    private static void loadProperties(String filename) throws IOException {
        try (InputStream stream = new FileInputStream(filename)) {
            props = new Properties();
            props.load(fileStream);
        }
    }
}