【问题标题】:How to synchronize two methods如何同步两种方法
【发布时间】:2012-04-10 09:52:30
【问题描述】:

我有以下 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。

这怎么可能?

【问题讨论】:

    标签: java multithreading collections concurrency


    【解决方案1】:

    这怎么可能?

    您的getMessages 方法只是在原始列表中返回一个视图。它不会创建列表的副本。因此,一个线程正在使用列表上的视图,而另一个线程正在修改列表 - 此时,您会遇到异常。

    来自List.subList 的文档:

    如果后备列表(即此列表)以除通过返回列表之外的任何方式进行结构修改,则此方法返回的列表的语义将变为未定义。 (结构性修改是改变这个列表的大小,或者以其他方式扰乱它,使得正在进行的迭代可能产生不正确的结果。)

    不清楚你在这里真正想要实现什么,但基本上你不能使用subList 神奇地创建一个线程安全列表:)

    【讨论】:

    • Jon skeet 回答了我的问题! :-O 问题自己解决了。 :-)
    • 但我的困惑是一个线程如何迭代视图,即当另一个线程正在修改它时调用 getMessages(),因为 getMessages() 和 addMessage() 都是同步的。为什么没有在方法级别发生同步?
    • @MonikaMichael:您在同步的结果上调用size() - 这就是问题所在。您在一个线程中创建子列表,释放锁,另一个线程获取锁,在底层列表上调用add()(这会使视图无效),然后在第一个线程中调用size()
    • 你能不能也回答一下这个问题:-( - stackoverflow.com/questions/9809100/…
    • 你能建议解决这个问题吗?我将编写两个公开这些方法的 REST 服务。不太确定如何以最佳方式同步。
    【解决方案2】:

    最简单的做法是创建一个组合方法

    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();
    }
    

    您不能安全地从同步块返回列表或子列表。您可以退还一份副本,但您只需要大小即可。

    【讨论】:

    • 那么在 addMessage() 方法中不是已经发生了。我在 getMessages() 上调用 size() 作为测试用例。这不是唯一的处理要求。
    • 您可以将需要同步的工作组合成一个方法,并且永远不会返回原始集合。顺便说一句:测试用例测试其结果,如果代码不正确,应该会失败。我建议您首先编写一个失败的测试,然后修复代码以使测试通过。一个从不失败的测试是没有用的(当然除了作为一个例子)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-25
    • 1970-01-01
    • 1970-01-01
    • 2021-04-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多