2012-04-10 72 views
2

我已经在Java聊天服务器应用程序下面的代码 -如何同步两种方法

public synchronized List<ChatMessage> getMessages(int messageNumber) { 
    return messages.subList(messageNumber + 1, messages.size()); 
} 

public synchronized int addMessage(ChatMessage c) { 
    messages.add(c); 
    return messages.size()-1; 
} 

我有以下的测试代码 -

public static void main(String[] args) { 
    final ChatRoom c = new ChatRoom(); 
    Thread user1 = new Thread(new Runnable() { 
     public void run() { 
      for(int i=0;i<1000;i++) { 
       c.addMessage(new ChatMessage()); 
       c.getMessages(0); 
      } 
     } 
    }); 
    Thread user2 = new Thread(new Runnable() { 
     public void run() { 
      for(int i=0;i<1000;i++) { 
       c.addMessage(new ChatMessage()); 
       c.getMessages(0).size(); 
      } 
     } 
    }); 
    user1.start(); 
    user2.start(); 
} 

我得到一个ConcurrentModificationException的。

这怎么可能?

回答

6

这怎么可能?

getMessages方法只是返回原始列表上的视图。它不创建列表的副本。所以一个线程在列表上使用一个视图,而另一个线程修改列表 - 在这一点上,你会得到异常。

从文档为List.subList

通过该方法变得不确定返回的列表中的语义如果支持列表(即,该列表)在比通过返回的列表之外的任何方式在结构上被修改。 (结构上修改是指更改大小此列表中,或者以其他方式干扰它以这样的方式,在正在进行的迭代产生不正确的结果。)

目前还不清楚是什么你真的想在这里实现,但从根本上说,你不能使用subList神奇地创建一个线程安全的名单:)

+2

乔恩斯波特回答了我的问题! :-O问题本身就解决了。 :-) – 2012-04-10 09:59:17

+0

但是我的困惑是一个线程如何迭代视图,即当另一个线程修改它时调用getMessages(),因为getMessages()和addMessage()都是同步的。为什么同步不会在方法级别发生? – 2012-04-10 10:02:01

+0

@MonikaMichael:你正在同步的* result *上调用'size()' - 这就是问题所在。您在一个线程中创建子列表,释放锁,另一个线程获取锁,在底层列表上调用add()方法(使视图无效),然后在第一个线程中调用size()。 – 2012-04-10 10:05:55

1

做最简单的事情是创建相结合的方法

public synchronized int addMessageAndGetCount(ChatMessage c) { 
    messages.add(c); 
    return messages.size(); 
} 

public static void main(String... args) { 
    final ChatRoom c = new ChatRoom(); 
    final Runnable runner = new Runnable() { 
     public void run() { 
      for(int i = 0; i < 1000; i++) { 
       c.addMessageAndGetCount(new ChatMessage()); 
      } 
     } 
    }; 
    new Thread(runner).start(); 
    new Thread(runner).start(); 
} 

你不能安全地返回一个列表或子列表一个同步块。您可以返回一份副本,但所需的只是尺寸。

+0

好吧,这不是已经发生在addMessage()方法中。我在getMessages()上调用size()作为测试用例。这不是唯一的处理要求。 – 2012-04-10 10:29:26

+0

您可以将您需要同步的工作组合到方法中,并且永不返回原始集合。顺便说一句:测试用例测试其结果,如果代码不正确,应该会失败。我建议你先写一个失败的测试,然后修复代码,以便测试通过。永远不会失败的测试是没有用的(除非作为一个例子当然) – 2012-04-10 10:38:15