1

这是我第一次尝试编写一个使用多线程的程序,所以我有几个关于在我的程序中使用并发的问题。

我的程序从 Web UI 获取用户输入,然后使用该用户输入启动一个进程。我知道我必须使用并发,因为这个过程需要一个多小时,而且我不可能让用户在开始下一个过程之前等待一个过程完成。

以下简化代码处理用户输入,然后启动该过程。

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String myInput = request.getParameter("input");
    Thread t = new Thread(new MyRunnable(myInput));
    t.start();

    response.setContentType("text/html");
    PrintWriter out = response.getWriter();
    out.println("Process started!");
    out.close();
}

以下代码是我实际过程的简化。

public class MyRunnable implements Runnable {

    private static HashMap<String,String> mapOfConstants = null;

    private String member;

    public MyRunnable(String member) {
        this.member = member;
    }

    @Override
    public void run() {
        if (mapOfConstants == null) init();
        // and so on...
    }

    private void init() {
        mapOfConstants = new HashMap<String,String>();
        mapOfConstants.put("LOCATION", "http://localhost/folder");
        // and so on...
    }

}

在上面的代码中,我打算将一系列占位符定义为一个常量,它们将存储在 HashMap 中mapOfConstants

编辑:最终我可能想让这个地图的初始化从其他地方获取值,比如一个文本文件。

我的代码是否实现了在 的所有实例之间共享此占位符映射的目的,MyRunnable只执行一次此初始化过程?

4

5 回答 5

1

我知道这不会受到强烈的欢迎,但你的代码几乎没问题。尽管不是严格的线程安全(就仅加载一次属性而言),但它仍然是正确的(就不创建损坏的数据而言)。

最大的变化是这样的:

private void init() {
    HashMap<String,String> tempMap = new HashMap<String,String>(); // <--- new object assigned to a placeholder variable
    tempMap.put("LOCATION", "http://localhost/folder");
    // and so on...
    mapOfConstants = Collections.unmodifiableMap(tempMap); // <--- atomic assignment here
}

假设这mapOfConstants实际上是一些标准的属性集,它们将从文件中加载并且永远不会更改,那么最大的“风险”是前几个任务都会认为地图为空并加载它。在编写代码时,进一步的风险是多个线程会同时修改它。使用上面修改过的代码,可能会有多个版本的地图,但所有版本都是正确的并且不会损坏,因为地图是原子分配的。最终,JVM 将整理出当前与该静态成员关联的映射是哪个映射,并且任何其他副本都将作为垃圾收集。

于 2013-07-02T21:03:59.790 回答
1

如果您想在所有用户之间共享常量,那么您就在正确的道路上,但是您必须使用synchronize您的代码。

编写代码最简单的方法synchronize是编写

public synchronized void run() {
}

请阅读一些关于 Java 同步的教程,因为这是 Java 中的一个雷区,即使是经验丰富的开发人员有时也会遇到问题。

对于你的第二个问题:请写一个新的。

于 2013-07-02T09:29:20.707 回答
1

我正在回答您的问题 1,您应该为 2 发布另一个问题。

我的代码是否实现了在 MyRunnable 的所有实例之间共享此占位符映射的目的,只执行一次此初始化过程?

是的,但它不是线程安全的。所以你有两个选择:

我的回答假设您不想在运行时更改映射内容,因为您已经告知它是常量映射。

  • 选项1:制作地图final并在块中使用Collections.unmodifiableMap和初始化它static,这也使您的代码线程安全。

  • 选项 2:(同步)如果你想使用这里显然不需要的延迟初始化,那么你必须让你的代码线程安全。您的代码不是线程安全的。

原因:多个运行线程可以看到map为null,调用init,会多次初始化map。使用synchronized块。

//keeping map `volatile`
private static volatile HashMap<String,String> mapOfConstants = null;

...
if(map == null)
  synchronized(SomeClass.class){
     if(map == null){
        init();
     }
  }
于 2013-07-02T09:30:40.803 回答
0

是的,您可以初始化一个静态成员并在线程之间共享它,但前提是您必须谨慎行事。例如,所有init()调用站点必须同步;也就是说,init()只能从构造函数或同步方法中调用。或者,您可以在静态初始化块中初始化mapOfConstants变量。无论您使用哪种方法,您可能还需要考虑将java.util.concurrent.ConcurrentHashMap实现作为mapOfConstants变量的具体类型,因为这样可以避免以后的麻烦。

不小心可能会导致您遇到“双重检查锁定”竞争条件。此外,初始化静态引用变量通常被认为是一种反模式,因为在这种情况下,映射中的条目可以在程序的一次激活内无限制地增长。通常,这种引用的唯一可接受的用法是内容是否是恒定的,或者内容是否随着时间的推移而增长非常缓慢(以对数方式思考)。对数增长可以通过使用java.util.WeakHashMap来帮助,或者,如果做不到,明智地使用弱引用

到目前为止,重点似乎是初始化静态mapOfConstants变量。但这忘记了地图的全部目的是(通常)在最好的情况下存储一些东西以供以后在 O(1) 时间内检索。请记住,当存储和检索操作(在mapOfConstants变量上)跨越线程边界时,这些操作也必须同步。一个线程的不同步、添加或编辑可能会被其他线程遗漏,这可能会影响您程序的数据完整性。

于 2013-07-02T20:49:01.200 回答
0

首先,不要在run()方法中初始化地图,不能保证初始化只发生一次。当然,在这种情况下,无论您创建该映射多少次,最终都会将一个设置为静态引用,而其他的将被 GCed。它只是不漂亮。我建议静态初始化块:

private final static Map<String,String> mapOfConstants;
static {
    Map<String, String> map = new HashMap<String, String>();
    // initialize map.
    map.put("", "");
    ...

    // convert the map into unmodifiable
    mapOfConstants = Collections.unmodifiableMap(map);
}

但是,还有另一种方法可以在多个线程之间共享常量映射。由于您正在考虑从文本文件中加载常量,您是否考虑过从 Runnable 中提取静态引用,在其他地方初始化映射,然后传入引用?构造函数采用额外的地图引用。

public class MyRunnable implements Runnable {

    private final Map<String,String> mapOfConstants = null;

    private String member;

    public MyRunnable(String member, Map<String,String> mapOfConstants) {
        this.member = member;
        this.mapOfConstants = mapOfConstants;
    }
    ....
}

然后在您的 servlet 中,从说 a 获取地图参考MapOfConstantsFactory

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws     ServletException, IOException {
    Map<String, String> sharedMapOfConstants = MapOfConstantsFactory.getMapOfConstants();
    String myInput = request.getParameter("input");
    Thread t = new Thread(new MyRunnable(myInput, sharedMapOfConstants));
    t.start();

    response.setContentType("text/html");
    PrintWriter out = response.getWriter();
    out.println("Process started!");
    out.close();
}

这样,您可以针对不同的常量配置编写测试,而无需修改 Runnable 类。

于 2013-07-02T09:31:17.070 回答