【问题标题】:Can I initialize a static member once and share it across threads?我可以初始化一个静态成员并跨线程共享它吗?
【发布时间】:2013-07-02 09:23:01
【问题描述】:

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

我的程序从 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...
    }

}

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

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

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

【问题讨论】:

  • 对于初学者,您可以将地图设为final 并在声明时对其进行初始化。
  • 请不要在一个 Stack Overflow 问题中要求多个答案。编辑您的问题以仅包含一个问题,并在必要时发布后续问题。
  • 编辑了我的问题并阐明了我的意图

标签: java multithreading concurrency static


【解决方案1】:

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

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

public synchronized void run() {
}

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

第二个问题:请写一个新问题。

【讨论】:

    【解决方案2】:

    我正在回答您的问题 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();
         }
      }
    

    【讨论】:

    • 这种双重检查锁定不应该被弃用吗? cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
    • @infgeoax 请参阅使用 volatile 修复双重检查锁定的部分,它对 helper 的作用我使用了 map 而不是 Helper。我认为这应该有效。还是告诉我,如果它坏了,我会修复它的代码。
    • 最终制作地图如何使代码线程安全?我仍然可以在运行时更改地图的内容,不是吗?我就是无法用其他地图替换地图?
    • @DaveHowes 问题说它是常数的地图。所以我假设它只在初始化时改变,而不是在运行时改变。
    • 很公平 - 只是想痛一下我对 final 的理解是正确的。
    【解决方案3】:

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

    最大的变化是这样的:

    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 将整理出当前与该静态成员关联的映射是哪个映射,并且任何其他副本都将作为垃圾收集。

    【讨论】:

      【解决方案4】:

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

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

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

      【讨论】:

        【解决方案5】:

        首先,不要在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 中,从 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 类。

        【讨论】:

        • 1.允许多次创建地图本身不是问题,这是地图静态分配然后填充的事实。如果首先填充本地范围映射,然后将其分配给静态范围,那么存在多少相同映射的版本并不重要(最终 JVM 会对其进行排序)。 2. 使用final不足以保护这张地图,地图还需要不可修改。 3.同步地图在这里没有帮助,并发读取不是问题,并且OP暗示地图不会改变。 4.没有解释为什么最后一个sn-p被破坏了,也是N/A
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-12-30
        • 1970-01-01
        • 2018-12-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多