Java同步以避免ConcurrentModificationExceptions?
我很难让我的程序正常工作。简而言之,我的程序由几个初始线程组成:Main,MessageReceiver,Scheduler(使用Quartz包)和Scheduler线程调度的两种线程类型:TraceReader和ScheduledEvent。现在,当TraceReader被触发时,它会读取一个特殊的跟踪文件,并使用开始时间,重复间隔(500ms到1秒)和结束时间安排事件。目前,可以安排约140个事件同时触发,这会导致大量的ConcurrentModificationException错误。现在一些代码:Java同步以避免ConcurrentModificationExceptions?
public class Client { //main class
public static volatile HashMap<Integer, Request> requests;
public static class Request{
String node;
int file_id;
long sbyte;
int length;
int pc_id;
public Request(){
}
}
public static synchronized void insertRequest(int req_nr, String node, int file_id, long sbyte, int length, int pc_id) {
Request tempr = new Request();
tempr.node = node;
tempr.file_id = file_id;
tempr.sbyte = sbyte;
tempr.length = length;
tempr.pc_id = pc_id;
requests.put(req_nr, tempr);
}
public static synchronized void doSynchronized(int req_nr, String node, int file_id, long sbyte, int length, int pc_id) {
reqnr++;
String r = "P" + reqnr + "," + file_id + "," + Long.toString(sbyte) + "," + length;
insertRequest(Client.reqnr, node, file_id, sbyte, length, pc_id);
}
public class ScheduledEvent implements Job {
public synchronized boolean isRequested(long sbyte, int length, int file_id, String node) {
Request req;
Iterator<Integer> it = Client.requests.keySet().iterator();
while (it.hasNext()) {
req = Client.requests.get(it.next());
if (req.node.equals(node) && req.file_id == file_id && hits(req.sbyte, req.length, sbyte, length)) {
return true;
}
}
return false;
}
}
所以我基本上得到了ScheduledEvent类的isRequested方法的错误。由于有超过100个并发线程,我认为由于其他线程正在使用Client.doSynchronized()而其他线程尝试在isRequested方法中迭代请求对象这一事实导致的错误。有没有办法让线程访问同步的对象,而不使用阻塞(Thread.join()等)?
您的代码的基本问题是您的对象在不同的对象(锁)上同步。为了解决这个问题,而不是宣布同步的方法 - 其同步类的对象 - 同步的请求对象本身:
public class Client { //main class
public static void insertRequest(int req_nr, String node, int file_id, long sbyte, int length, int pc_id) {
...
synchronized (requests) {
requests.put(req_nr, tempr);
}
...
}
public class ScheduledEvent implements Job {
public boolean isRequested(long sbyte, int length, int file_id, String node) {
...
synchronized (requests) {
while (it.hasNext()) {
req = Client.requests.get(it.next());
if (req.node.equals(node) && req.file_id == file_id && hits(req.sbyte, req.length, sbyte, length)) {
return true;
}
}
}
...
}
}
您可以用ConcurrentHashMap
取代HashMap
。
http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/ConcurrentHashMap.html#keySet%28%29
keySet()返回包含在此映射的关键 的一组视图。该集合是由地图支持的 ,所以对地图的更改反映在该集合中,反之亦然 。该设置支持元素 删除,该操作通过Iterator.remove,Set.remove, removeAll,retainAll和清除 操作从该映射中删除 对应的映射, 。它不支持 add或addAll操作。视图的 返回的迭代器是一个“弱 一致”迭代器,不会 抛出ConcurrentModificationException, 并确保遍历元素 构造后的 迭代器的存在,还可能(但不是 保证)反映任何 施工后的修改。
ConcurrentModificationException
是由于地图密钥集上迭代器的快速失败特性而发生的。
您应该使用ConcurrentHashMap
而不是HashMap
,这将允许您摆脱很多手动同步。
UPDATE:
你的问题再次看,我有几个问题/建议:
- 是否
ScheduledThreadPoolExecutor
你需要什么?我强烈建议在可能的情况下使用java.util.concurrent
中提供的更高级别的并发结构,因为在刚开始使用基本并发基元时很难获得并发性。 - 假设上一个问题的答案是否定的,是否有理由不能使用
Queue
而不是Map
键盘上的请求编号使用经典的生产者 - 消费者模式?看起来像BlockingQueue
实现之一将是理想的。
的ConcurrentHashMap不仅使同步的实例中操作,但没有办法,以确保不同的线程在同一时间在同一实例不调用不同的操作,即线程1调用地图。 add()while thread2。正在迭代它。 – 2009-09-29 16:19:10
thread1中的迭代器会在thread1添加其值之前看到'keySet'的快照。 – pjp 2009-09-29 16:53:12
我不知道ScheduledThreadPoolExecutor存在,因此我使用Quartz库(www.opensymphony.com/quartz/)。如果我想切换到ScheduledThreadPoolExecutor,我将不得不编辑整个三个程序......所以我宁愿坚持Quartz。至于问题2的答案,我没有清楚地理解你的问题,但我需要请求号码,因为这个程序通过网络向另一个请求发送请求,所以我不在我的项目中使用过程调用。 – Azimuth 2009-09-29 16:58:10
你是A)误解volatile关键字的用法,以及B)误解使用synchronized。这可能是一个棘手的问题,所以我会尝试并简要概述两者。
挥发性告诉可以由多个线程来更新该变量被称为编译器,因此它必须确保每个线程看到正确的值时,它读/写至该变量。你滥用易失性的原因是,当你声明一个易变的对象你告诉编译器引用是易失性的。也就是说,指向HashMap的指针可以在任何时候改变(例如,如果你的请求=新的HashMap(),指针会改变)。然而,你并没有在程序中随时改变对请求的引用,所以它不会将其声明为volatile,正如在另一个回答中提到的,你应该声明它是最终的。
同步是本质上是一个锁()一些物体上的快捷方式的关键字。当您在类的实例的上下文中使用同步时,synchronized会锁定实例上的。即:
class X {
public synchronized doStuff() {
...
}
}
X instance = new X();
instance.doStuff();
将具有完全相同的效果:
class X {
public doStuff() {
lock(this) { // or lock(instance)
...
}
}
}
如果您使用一个静态上下文但是同步,该锁由同步生成没有实例来锁定,所以相反,它会锁定类类型的实例。为了简化,对于使用同步语句的每个类,在静态上下文中的同步将在所有情况下锁定锁1,并且每次在非静态上下文中同步锁定在锁2上。这意味着没有多线程安全除非您确定事情正在使用相同的锁。
使代码工作的一种方式是明确使用锁(请求)语句,以便它们都共享相同的锁。实际上锁定要使用的对象并不总是最佳实践,通常人们会创建一个新对象来锁定,而不是用于任何其他目的以避免混淆。
其他回复提到使用ConcurrentHashMap,这是一个有效的解决方案,尽管这些答案没有解决根本问题。但是,如果您决定使用ConcurrentHashMap,请确保您了解它的安全保证,并且不会对并发修改提供任何帮助。它可能并不总是像你期望的那样。
一些风格要点:Request的成员应该是'private',并添加getter和setter。请求也应该是“私人”的。你为什么使用一堆'静态'方法?他们不会帮你建立一些漂亮和可扩展的东西.. – pjp 2009-09-29 16:15:57
感谢您的意见。我在Java编程方面并不擅长,并且不太了解这些关键字的作用。我大部分方法是静态的原因是我以前得到了“静态访问非静态方法”的错误。 – Azimuth 2009-09-29 16:20:38
如果我将声明Requests private,我将无法从另一个类访问它(例如ScheduledEvent)是不是? – Azimuth 2009-09-29 16:21:32