Java同步以避免ConcurrentModificationExceptions?

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()等)?

+0

一些风格要点:Request的成员应该是'private',并添加getter和setter。请求也应该是“私人”的。你为什么使用一堆'静态'方法?他们不会帮你建立一些漂亮和可扩展的东西.. – pjp 2009-09-29 16:15:57

+0

感谢您的意见。我在Java编程方面并不擅长,并且不太了解这些关键字的作用。我大部分方法是静态的原因是我以前得到了“静态访问非静态方法”的错误。 – Azimuth 2009-09-29 16:20:38

+0

如果我将声明Requests private,我将无法从另一个类访问它(例如ScheduledEvent)是不是? – Azimuth 2009-09-29 16:21:32

您的代码的基本问题是您的对象在不同的​​对象(锁)上同步。为了解决这个问题,而不是宣布同步的方法 - 其同步类的对象 - 同步的请求对象本身:

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; 
     } 
     } 
    } 
    ... 
} 
} 
+0

我试过,但它说“在非最终字段上同步” – Azimuth 2009-09-29 16:03:02

+0

最后声明请求;无论如何你可能不会改变那个参考。 – Zed 2009-09-29 16:04:41

+0

让它'final'然后'公众最终静态挥发的HashMap 请求;' – pjp 2009-09-29 16:05:40

您可以用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是由于地图密钥集上迭代器的快速失败特性而发生的。

+0

感谢您的答复。但据我所知,ConcurrentHashMap比HashMap慢。你知道它是如此吗? – Azimuth 2009-09-29 16:13:06

+1

它肯定比HashMap周围的“同步”更快。这是因为ConcurrentHashMap在HashMap中的单个桶上具有更精细的级别锁定,而不是整个地图上的一个锁定。然而,你的代码正在做一个线性搜索的值,所以它不会那么快。 – pjp 2009-09-29 16:31:33

+0

谢谢你的提示。似乎我将不得不编辑我的所有代码。但不管怎么说,它有许多工作要做... – Azimuth 2009-09-29 16:39:14

您应该使用ConcurrentHashMap而不是HashMap,这将允许您摆脱很多手动同步。

UPDATE:

你的问题再次看,我有几个问题/建议:

  1. 是否ScheduledThreadPoolExecutor你需要什么?我强烈建议在可能的情况下使用java.util.concurrent中提供的更高级别的并发结构,因为在刚开始使用基本并发基元时很难获得并发性。
  2. 假设上一个问题的答案是否定的,是否有理由不能使用Queue而不是Map键盘上的请求编号使用经典的生产者 - 消费者模式?看起来像BlockingQueue实现之一将是理想的。
+0

的ConcurrentHashMap不仅使同步的实例中操作,但没有办法,以确保不同的线程在同一时间在同一实例不调用不同的操作,即线程1调用地图。 add()while thread2。正在迭代它。 – 2009-09-29 16:19:10

+0

thread1中的迭代器会在thread1添加其值之前看到'keySet'的快照。 – pjp 2009-09-29 16:53:12

+0

我不知道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,请确保您了解它的安全保证,并且不会对并发修改提供任何帮助。它可能并不总是像你期望的那样。