2009-09-29 131 views
0

我很難讓我的程序正常工作。簡而言之,我的程序由幾個初始線程組成: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

回答

5

您的代碼的基本問題是您的對象在不同的​​對象(鎖)上同步。爲了解決這個問題,而不是宣佈同步的方法 - 其同步類的對象 - 同步的請求對象本身:

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

1

您可以用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

1

您應該使用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

1

你是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,請確保您瞭解它的安全保證,並且不會對併發修改提供任何幫助。它可能並不總是像你期望的那樣。