【问题标题】:Unable to understand the workings of volatile fields and how they work with multiple threads when sharing values无法理解 volatile 字段的工作原理以及它们在共享值时如何与多个线程一起工作
【发布时间】:2018-10-10 08:38:38
【问题描述】:

我正在开发一个程序,该程序需要从多个线程的选项文件中加载相同的信息。因此,我基于抽象模型为每个文件制作了一个简单的类表示:(下图是其中许多类的一个示例)

package OptionManager;

import java.util.Properties;

public class EngineOptions extends AbstractOptions{
    //values
    private static String debugEnabled;
    private static String debugAvgLoadtime;
    private static String showShaderUsed;
    private static String mainLanguage;

    //keys
    public static final String DEBUGENABLED_KEY = "debugEnabled";
    public static final String DEBUGAVGLOADTIME_KEY = "debugAvgLoadtime";
    public static final String SHOWSHADERUSED_KEY = "showShaderUsed";
    public static final String MAINLANGUAGE_KEY = "mainLanguage";

    public static String getProperty(String key) {
        return properties.getProperty(key);
    }

    public static void loadFromFile(String filename) {      
        OptionReader loader = new OptionReader(filename);
        //load properties
        debugEnabled = loader.getProperty(DEBUGENABLED_KEY);
        debugAvgLoadtime = loader.getProperty(DEBUGAVGLOADTIME_KEY);
        showShaderUsed = loader.getProperty(SHOWSHADERUSED_KEY);
        mainLanguage = loader.getProperty(MAINLANGUAGE_KEY);

        properties.put(DEBUGENABLED_KEY, debugEnabled);
        properties.put(DEBUGAVGLOADTIME_KEY, debugAvgLoadtime);
        properties.put(SHOWSHADERUSED_KEY, showShaderUsed);
        properties.put(MAINLANGUAGE_KEY, mainLanguage);
    }
}

这是它使用的抽象类:

package OptionManager;

import java.util.Properties;

public abstract class AbstractOptions {
    protected static volatile Properties properties;

    public static void setupProperties() {
        properties = new Properties();
    }

    public static void setupProperties(Properties properties) {}

    public static void setProperty(String key, String value) {
        if(properties.getProperty(key) == null) {
            //throw exception.
        }
        properties.setProperty(key, value);
    }

    public static String getProperty(String key) {
        System.out.println(properties);
        return properties.getProperty(key);
    }

    //public static void loadFromFile(String filename) {}
}

我能够在主线程(由 JVM 创建以启动程序)中加载选项文件,并在该线程中使用该系统来获取我想要的所有选项。事实上,作为一个测试,我每次访问一个提供以下输出的选项时都会在 EngineOptions 类中打印整个选项列表(当获取 mainLanguage 选项时):

{mainLanguage=language_IT, debugAvgLoadtime=1, debugEnabled=1, showShaderUsed=1} 语言_IT

但是,当我尝试在另一个线程中访问相同的选项(由主线程创建并在我打印上面的输出后启动)时,我得到了这个:

{} 空

这让我相信,对于每个线程,我的静态字段的值是不共享的。所以我找到了this answer,它建议使用'volatile'来解决问题。然而这并没有奏效。

另一个选择是在每个线程中加载选项文件,但我不想这样做,因为我必须为每个线程多次加载选项文件的原因是我首先创建这个系统的原因。

如何解决这个问题并在多个线程上共享选项的值?

编辑:

我如何创建一个新的选项列表:

OptionHandler.addOptionFile(OptionHandler.ENGINE_OPTION_ID, new EngineOptions(), "EngineOptions");
OptionHandler.loadOptionListFromFile(OptionHandler.ENGINE_OPTION_ID, OptionHandler.ENGINE_OPTION_TYPE);

我如何从线程中调用选项:

String currentLang = OptionHandler.getProperty(EngineOptions.MAINLANGUAGE_KEY, OptionHandler.ENGINE_OPTION_ID);
        System.out.println(currentLang);

编辑 2:

package OptionManager;

import java.util.HashMap;
import java.util.Map;

public class OptionHandler {
    private static HashMap<Integer, AbstractOptions> optionList;
    private static HashMap<Integer, String> optionFilename;

    //OptionFIleID's (Starting from 101 to 199)
    public static final int GRAPHIC_OPTION_ID = 101;
    public static final int ENGINE_OPTION_ID = 102;
    public static final int CURRENT_LANGUAGE_ID = 103;

    public static final int GRAPHIC_OPTION_TYPE = 201;
    public static final int ENGINE_OPTION_TYPE = 202;
    public static final int CURRENT_LANGUAGE_TYPE = 203;

    public static void setupOptions() {
        optionList = new HashMap<Integer, AbstractOptions>();
        optionFilename = new HashMap<Integer, String>();
    }

    public static void addOptionFile(int id, AbstractOptions options, String filename) {
        options.setupProperties();
        optionList.put(id, options);
        optionFilename.put(id, filename);
    }

    public static String getProperty(String optionKey, int optionFileID) {
        return optionList.get(optionFileID).getProperty(optionKey);
    }

    public static void loadOptionListFromFile(int id, int type) {
        System.out.println(optionFilename.get(id));
        if(type == GRAPHIC_OPTION_TYPE)
            GraphicOptions.loadFromFile(optionFilename.get(id));
        if(type == ENGINE_OPTION_TYPE)
            EngineOptions.loadFromFile(optionFilename.get(id));
        if(type == CURRENT_LANGUAGE_TYPE)
            CurrentLanguage.loadFromFile(optionFilename.get(id));
    }
}

【问题讨论】:

  • 你能把你正在发生的代码放在单独的线程中吗?
  • 另外,为什么你的Properties propertiesdebugEnabled等都是static,当它们被实例方法填充时?
  • 我添加了显示我如何加载选项的编辑
  • @jbx 我已将所有方法更改为静态(我想它们应该是),但我仍然得到相同的输出
  • 你能告诉我们你的OptionHandler是什么样子吗?

标签: java multithreading static volatile


【解决方案1】:

这是一个线程同步问题,如果你能保证主线程中的加载选项动作发生在其他线程中的读取动作之前,那么使用volatile是没有问题的。如果不能,最好使用CountDownLatch等线程同步api。 这是我对你代码的修改,关注OptionHandler类,我的输出是正确的。

import java.util.Properties;

public abstract class AbstractOptions {
    protected volatile Properties properties;

    public void setupProperties() {
        properties = new Properties();
    }

    public void setupProperties(Properties properties) {}

    public void setProperty(String key, String value) {
        if(properties.getProperty(key) == null) {
            //throw exception.
        }
        properties.setProperty(key, value);
    }

    public String getProperty(String key) {
        System.out.println(properties);
        return properties.getProperty(key);
    }

    public abstract void loadFromFile(String filename);
}


public class EngineOptions extends AbstractOptions{
    //values
    private String debugEnabled;
    private String debugAvgLoadtime;
    private String showShaderUsed;
    private String mainLanguage;

    //keys
    public static final String DEBUGENABLED_KEY = "debugEnabled";
    public static final String DEBUGAVGLOADTIME_KEY = "debugAvgLoadtime";
    public static final String SHOWSHADERUSED_KEY = "showShaderUsed";
    public static final String MAINLANGUAGE_KEY = "mainLanguage";

    //public String getProperty(String key) {
    //return properties.getProperty(key);
    //}

    public void loadFromFile(String filename) {
        //OptionReader loader = new OptionReader(filename);
        //load properties
        debugEnabled = "language_IT";//loader.getProperty(DEBUGENABLED_KEY);
        debugAvgLoadtime = "1";//loader.getProperty(DEBUGAVGLOADTIME_KEY);
        showShaderUsed = "1";//loader.getProperty(SHOWSHADERUSED_KEY);
        mainLanguage = "1";//loader.getProperty(MAINLANGUAGE_KEY);

        properties.put(DEBUGENABLED_KEY, debugEnabled);
        properties.put(DEBUGAVGLOADTIME_KEY, debugAvgLoadtime);
        properties.put(SHOWSHADERUSED_KEY, showShaderUsed);
        properties.put(MAINLANGUAGE_KEY, mainLanguage);
    }
}

import java.util.HashMap;
import java.util.concurrent.CountDownLatch;

public class OptionHandler {
    private static HashMap<Integer, AbstractOptions> optionList;
    private static HashMap<Integer, String> optionFilename;
    private static CountDownLatch sync;

    static {
        setupOptions();
    }

    //OptionFIleID's (Starting from 101 to 199)
    public static final int GRAPHIC_OPTION_ID = 101;
    public static final int ENGINE_OPTION_ID = 102;
    public static final int CURRENT_LANGUAGE_ID = 103;

    public static final int GRAPHIC_OPTION_TYPE = 201;
    public static final int ENGINE_OPTION_TYPE = 202;
    public static final int CURRENT_LANGUAGE_TYPE = 203;

    public static void setupOptions() {
        optionList = new HashMap<Integer, AbstractOptions>();
        optionFilename = new HashMap<Integer, String>();
        //initialize
        sync = new CountDownLatch(1);
    }

    public static void addOptionFile(int id, AbstractOptions options, String filename) {
        options.setupProperties();
        optionList.put(id, options);
        optionFilename.put(id, filename);
    }

    public static String getProperty(String optionKey, int optionFileID) {
        try {
            //await when the property is not ready yet
            sync.await();
        } catch (InterruptedException e) {
            //log("thread was interrupted")
            Thread.currentThread().interrupt();
        }
        return optionList.get(optionFileID).getProperty(optionKey);
    }

    public static void loadOptionListFromFile(int id, int type) {
        System.out.println(optionFilename.get(id));
//        if(type == GRAPHIC_OPTION_TYPE)
//            GraphicOptions.loadFromFile(optionFilename.get(id));
        if(type == ENGINE_OPTION_TYPE)
            optionList.get(id).loadFromFile(optionFilename.get(id));
//        if(type == CURRENT_LANGUAGE_TYPE)
//            CurrentLanguage.loadFromFile(optionFilename.get(id));
        //Notify other threads that the property is ready
        sync.countDown();
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            String currentLang = 
                  OptionHandler.getProperty(EngineOptions.MAINLANGUAGE_KEY, 
                  OptionHandler.ENGINE_OPTION_ID);
            System.out.println(currentLang);
        }).start();
        Thread.sleep(3000);
        OptionHandler.addOptionFile(OptionHandler.ENGINE_OPTION_ID, new EngineOptions(), "EngineOptions");
        OptionHandler.loadOptionListFromFile(OptionHandler.ENGINE_OPTION_ID, OptionHandler.ENGINE_OPTION_TYPE);
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-11-17
    • 1970-01-01
    • 2014-04-08
    • 2012-01-24
    • 1970-01-01
    • 1970-01-01
    • 2011-11-24
    相关资源
    最近更新 更多