我目前正在用Java编写一个简单的聊天应用程序。它具有服务器应用程序和客户端应用程序,均使用Java编写并使用RMI进行通信。
在服务器上,有一个ArrayList
活动用户,该用户在用户上线和脱机时都会更新。与服务器的每个客户端连接都有其自己的线程。
建立新连接(即加入新客户端)后,将创建一个新线程,并在该线程内对其ArrayList
进行更新以反映新用户。同样,当连接ArrayList
断开时,会更新来反映断开连接。
显然,由于有很多客户端,因此将有很多并发访问权限ArrayList
。因此,我的问题是,是否应该ArrayList
同步访问/更新此方法的任何方法?
其他答案建议使用Collections.synchronizedList()
。
List<User> list = Collections.synchronizedList(new ArrayList<>());
这可能有效,但是您必须格外小心。这样的列表仅对于直接在该列表上的单个方法调用是线程安全的。但是,许多操作涉及列表上的多个操作,例如遍历列表。例如,一个人可能会编写以下代码,以向聊天中的每个用户发送一条消息:
for (User u : list)
u.sendMessage(msg);
如果在迭代过程中将用户添加到列表中或从列表中删除用户,则将失败并显示a ConcurrentModificationException
。发生这种情况是因为这种形式的for循环Iterator
从列表中获取一个,然后通过hasNext
andnext
调用重复访问该列表。可以在这些访问之间修改列表。如果列表被修改,则Iterator
检测到该列表并引发异常。
在Java SE 8中,可以改写以下内容:
list.forEach(u -> u.sendMesage(msg));
这是安全的,因为列表的锁定在整个forEach
通话过程中都会保持。
(另一个建议是使用aSet
代替List
。。由于其他原因,这可能是个好主意,但是它也遇到了与List
。相同的并发问题。)
解决此问题的最佳方法是创建自己的对象,该对象包含一个用户列表(或一组)和任何相关数据,并在用户容器和相关数据上定义一组特定的操作。然后,在容器对象的方法周围添加适当的同步。这不仅允许您列表或一组用户,还可以对其他数据进行线程安全更新。
最后,volatile
如果在构造时初始化了列表字段,则在使对象对其他线程可见之前就没有必要了。用RMI术语来说,如果在导出对象之前完成了构造,那么您是安全的。最好将该字段设置final
为在初始化后知道所有其他线程都可以看到初始化值,并且此后不能更改该值。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句