简体   繁体   English

以下代码线程安全吗?

[英]Is the following code thread safe?

I think I have implemented the Double-checked locking pattern but not sure if it safe or it works as intended. 我想我已经实现了双重检查锁定模式,但不确定它是否安全或是否按预期工作。 Any other logic to implement the same would be really helpful. 任何其他实现相同逻辑的方法也将非常有帮助。

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

Generally multiple threads running often access the "getProperty" method as follows: 通常,运行的多个线程通常按如下方式访问“ getProperty”方法:

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

Atomic values guarantee to always return the complete latest value to all threads. 原子值保证始终将完整的最新值返回给所有线程。 This prevents a number of multi-threading issues in this case. 在这种情况下,这可以防止许多多线程问题。 A bit of synchronization is still required, but it can be limited to a minimum. 仍然需要一点同步,但是可以将其限制在最低限度。 See my implementation below: 请参阅下面的实现:

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.");
}

}

One drawback of the implementation is that one thread will get slowed down when it is selected to reload the properties from file. 该实现的一个缺点是,当选择从文件中重新加载属性时,一个线程将变慢。 A better approach would be to create a 'watchdog' thread/scheduled task that checks every (for example) five seconds if the properties-file has a changed modification date and then trigger a reload (in which case the AtomicReference for the Properties still comes in handy). 更好的方法是创建一个“看门狗”线程/计划任务,该任务每隔(例如)五秒钟检查一次属性文件的修改日期是否更改,然后触发重新加载(在这种情况下,仍然会出现属性的AtomicReference派上用场)。
Also keep in mind that there is a logical threading issue: if property values are interrelated (ie one value is only correct if another value is also updated), a reload could present a thread with old and new values that should not be mixed. 还要记住,存在一个逻辑线程问题:如果属性值是相互关联的(即,一个值仅在另一个值也被更新时才是正确的),重新加载可能会给线程提供带有旧值和新值的线程,不应将它们混合使用。 The only way around that is to keep a reference to one set of properties in methods that use the interrelated values of the properties (and a class like this with static methods and variables is not handy in such a situation). 唯一的解决方法是在使用属性的相互关联的值的方法中保留对一组属性的引用(在这种情况下,此类带有静态方法和变量的类并不方便)。

It is not safe as you have multiple variables which are read in a way which is not thread safe (ie access is not synchronized and they are not volatile). 这是不安全的,因为您有多个变量以非线程安全的方式读取(即,访问未同步并且它们不是易变的)。

It appears the workflow is mostly reads with a few writes. 看来工作流程大部分是读取操作,而有些写入操作。 I would suggest using a ReentrantReadWriteLock to synchronize access. 我建议使用ReentrantReadWriteLock来同步访问。

To have this working correctly with double-checked locking you must do two things: 要使此功能与双重检查锁定一起正常工作,您必须做两件事:

  • private static Properties props must be declared volatile; private static Properties props必须声明为volatile;
  • as already mentioned, synchronised(props) won't work in case props are null - you need to declare a special lock object field: 如前所述,在props为null的情况下,synced(props)将不起作用-您需要声明一个特殊的锁对象字段:

.

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

PS The lastReadTimestamp won't work also unless declared volatile. PS:除非声明为volatile,否则lastReadTimestamp也将不起作用。 Though this is not about double-checked locking anymore. 尽管这不再与再次检查锁定有关。

  1. To reload the properties, you don't need to re-initialize the props variable. 要重新加载属性,不需要重新初始化props变量。 Initialize the properties during the declaration statement itself will do. 在声明语句本身会初始化属性。 This will solve the problem of synchronizing with null. 这将解决与null同步的问题。
  2. Remove the initialization code in the loadProperties block. 在loadProperties块中删除初始化代码。
  3. remove the prop==null check outside and inside the synchronized block. 在同步块的内部和外部删除prop == null检查。
  4. Once that is done, your code will work exactly the way you want. 完成后,您的代码将完全按照您想要的方式工作。

    public class OnProperties { 公共类OnProperty {

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

Please accept that the double-checked locking idiom is broken and does not work (ie does not synchronize properly). 请接受再次检查的锁定习惯用法已损坏并且不起作用(即,无法正确同步)。 Even if you make it work using volatile (at the right place), it is far too complex for what you get. 即使您使用volatile (在正确的位置)使其工作,对于所获得的结果来说也太复杂了。

So my suggestion: Simply synchronize everything. 所以我的建议是:只需同步所有内容。 Then try and measure. 然后尝试测量。 If you find out that OnProperties is the bottleneck, consider more powerful/clever synchronization techniques and come back if necessary: 如果您发现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);
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM