这是我第一次尝试编写一个使用多线程的程序,所以我在程序中使用并发有几个问题。
我的程序从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
的所有实例之间共享此占位符映射的目的,只执行此初始化过程一次?
答案 0 :(得分:1)
如果你想在所有用户之间分享常数,那么你就走在正确的路径但,那么你必须synchronize
你的代码。
synchronize
代码的最简单方法是编写
public synchronized void run() {
}
请阅读一些关于Java同步的教程,因为这是Java中的一个雷区,即使是经验丰富的开发人员也会遇到问题。
关于第二个问题:请写一个新问题。
答案 1 :(得分:1)
我正在回答您的问题1,您应该发布另一个问题2。
我的代码是否达到了共享此占位符映射的目的 跨越MyRunnable的所有实例,执行此初始化过程 只有一次?
是的,但它不是线程安全的。所以你有两个选择:
我的回答是假设您不想更改地图内容 运行时,因为你已经告诉它它是一个常量图。
选项1:制作地图final
并使用Collections.unmodifiableMap
并在static
块中对其进行初始化,这样您的代码线程也会安全。
选项2 :(同步)如果你想使用延迟初始化,这里显然不需要,那么你必须使你的代码线程安全。 您的代码不是线程安全的。
原因:多个正在运行的线程可以将地图视为null并调用init,这将多次初始化地图。使用synchronized
阻止。
//keeping map `volatile`
private static volatile HashMap<String,String> mapOfConstants = null;
...
if(map == null)
synchronized(SomeClass.class){
if(map == null){
init();
}
}
答案 2 :(得分: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将整理当前与该静态成员关联的映射,并将任何其他副本收集为垃圾。
答案 3 :(得分: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中,从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类。
答案 4 :(得分:0)
是的,你可以初始化一次静态成员并在线程之间共享它,但前提是你要小心谨慎。例如,所有 init() 呼叫网站必须同步;也就是说, init() 只能从构造函数或同步方法中调用。或者,您可以在静态初始化程序块中初始化 mapOfConstants 变量。无论您使用哪种方法,您可能还需要将java.util.concurrent.ConcurrentHashMap实现视为 mapOfConstants 变量的具体类型,因为这样可以避免以后的麻烦。
未能适当注意可能会导致您遇到“双重检查锁定”竞争状况。此外,初始化静态引用变量通常被认为是反模式,因为在这种情况下,映射中的条目可以在程序的一次激活内无限制地增长。通常情况下,这种参考的唯一可接受的用法是,如果内容是,嗯,不变,或者内容增长非常缓慢 - 随着时间的推移,以对数方式思考。使用java.util.WeakHashMap可能会有助于对数增长,或者在weak references的明智使用下,可能会有助于对数增长。
到目前为止,重点似乎是初始化静态 mapOfConstants 变量。但是这忘记了地图的整个目的是(通常)存储一些东西,以便在最好的情况下在O(1)时间内进行后续检索。请记住,当存储和检索操作(在 mapOfConstants 变量上)跨越线程边界时,那些操作也必须同步。缺少同步,一个线程的添加或编辑可能会被其他线程遗漏,这可能会破坏您的程序的数据完整性。