RMI and Synchronization



我目前正在Java中编写一个简单的聊天应用程序。它具有用Java编写的服务器应用程序和客户端应用程序,并使用RMI进行通信。

在服务器上,有一个 ArrayList的活动用户,当用户访问和脱机时会进行更新。每个客户端连接到服务器都有其自己的线程。

建立了新的连接(即新客户端加入)时,创建了一个新线程,并且在此线程中,ArrayList会更新以反映新用户。同样,当连接丢失时,ArrayList会更新以反映断开连接。

显然,有很多客户将同时访问ArrayList。因此,我的问题是,是否应该同步访问/更新此ArrayList的任何方法?

使用Collections.synchronizedList()提出了其他答案。

List<User> list = Collections.synchronizedList(new ArrayList<>());

可能会工作,但是您必须非常小心。此列表只是直接在该列表上的单个方法调用的线程安全。但是,许多操作涉及列表中的多个操作,例如在列表上进行迭代。例如,可能会写下以下代码以向聊天中的每个用户发送消息:

for (User u : list)
    u.sendMessage(msg);

如果在迭代期间将用户添加到或从列表中删除,则将使用ConcurrentModificationException失败。之所以发生这种情况,是因为这种形式的For-loop从列表中获取Iterator,并通过hasNextnext调用重复访问列表。可以在这些访问之间修改列表。如果列表进行了修改,则Iterator检测到并抛出异常。

在Java SE 8中,可以写下以下内容:

list.forEach(u -> u.sendMesage(msg));

这是安全的,因为列表的锁在forEach调用的整个过程中保存。

(另一个建议是使用Set代替List。这可能是一个好主意,但由于其他原因,它与List相同的并发问题。)

处理此问题的最佳方法可能是创建自己的对象,包含用户和任何相关数据的列表(或集合),并在您的容器上定义了一组特定的操作用户和相关数据。然后,在容器对象的方法周围添加适当的同步。这允许除了您的列表或一组用户外,还允许对其他数据进行线程安全更新。

最后,如果在对象可见到其他线程之前,在施工时间初始初始化了列表字段,则不需要volatile。用RMI术语,如果在对象出口之前完成施工,则您是安全的。最好将字段 final做出,以便在初始化之后,已知所有其他线程都可以看到初始化值。

使用Collections.synchronizedList(new ArrayList<User>());

其次,您也可以做

volatile List<User> list = Collections.synchronizedList(new ArrayList<User>());

保证线程之间可见性。

注意:我还建议使用设置而不是列表以避免在任何错误或角案例中重复。请参阅以下代码以获取参考。

volatile Set<User> onlineUsers = Collections.synchronizedSet(new HashSet<User>());

如果要使用多个线程中的列表,则应使用Collections.synchronizedList()

此建议已经在此处给出。

最新更新