[英]Singleton service in Java and multithreading
我正在使用JAX-RS API开发Java服务。 我已经决定采用单例模式,但是现在我对如何管理并发性有所怀疑。
以下是我的代码的简化示例:
@Singleton
@Path("/")
public class NfvDeployer {
private static Map<String, List<String>> allocatedNodesOnHost;
private static Map<String, String> loadedHosts;
private static Map<String, String> loadedNodes;
...
@POST
@Path("nffgs/{id}/nodes")
@Produces(MediaType.APPLICATION_XML)
@Consumes(MediaType.APPLICATION_XML)
public MyNode postNodeOnNFFG(MyNode Node, @PathParam("id") String id) {
...
synchronized (this) {
...
allocatedNodesOnHost.get(H).add(Node.ID);
}
}
@GET
@Path("hosts/{id}/nodes")
@Produces(MediaType.APPLICATION_XML)
public MyNodes getNodeFromNFFG(@PathParam("id") String id) {
...
for(String S : allocatedNodesOnHost.get(id)) {
...
}
}
}
您认为这种方法可行吗? 所有GET请求应同时发生,而POST请求应被序列化。 这是正确的吗?
有些事情可能无法按预期工作。 首先,因为你可以声明你NfvDeployer
为@Singleton
,你不必严格,使所有领域的静态。 由于该框架将确保其中只有一个存在,因此应将其涵盖在内。
关于同步,按原样存在问题。 请考虑以下情况:写入列表中包含为allocatedNodesOnHost
的值的列表,并同时遍历同一列表。 您最终将得到ConcurrentModificationException
。 从Javadoc :
例如,通常不允许一个线程修改Collection而另一个线程对其进行迭代。
至少有两种解决方案,它们取决于您想要哪种担保。
选项1:一致的数据视图
假设列表的读者和列表的作者必须具有一致的列表视图。 含义:如果有人写一个新值是不正常的读者查看列表中包含的值的稍旧的版本。 为了解决这个问题,我们应该同步对地图的所有访问(及其列表值):
public MyNode postNodeOnNFFG(MyNode node, @PathParam("id") String id) {
synchronized (allocatedNodesOnHost) {
allocatedNodesOnHost.get(id).add(node.ID);
}
}
public MyNodes getNodeFromNFFG(@PathParam("id") String id) {
synchronized (allocatedNodesOnHost) {
for(String S : allocatedNodesOnHost.get(id)) {
...
}
}
}
您会在这里注意到,我是在allocatedNodesOnHost
而不是this
上同步。 通过this
同步,我们获得了非常粗糙的锁,它会影响我们可能设置的任何其他同步块。 同样,为了避免ConcurrentModificationExceptions,对allocatedNodesOnHost
NodesOnHost的所有访问都必须同步。
选项2:数据视图不一致
当我说“不一致”时,我并不是说错误,而是意味着它可能已经过时,仅在并发修改的情况下。 本质上,我们将在选项1中执行的同步卸载到JVM提供的将为我们完成的结构上。
首先,我们将使用ConcurrentHashMap
声明字段:
private Map<String, List<String>> allocatedNodesOnHost = new ConcurrentHashMap<>();
ConcurrentHashMap
为我们提供了一个地图,使我们可以在不进行同步的情况下读取和写入地图。 它避免了在可能的地方阻塞,因此,当您知道将有并发的读取器和写入器时,最好在多线程应用程序中使用它。
当我们在该映射中创建一个值时,将使用一个CopyOnWriteArrayList
,它被描述为:
ArrayList的线程安全变体,其中所有可变操作(添加,设置等)都通过对基础数组进行全新复制来实现。 通常这样做的成本太高,但是在遍历操作的数量远远超过变异的情况下,它可能比替代方法更有效,并且在您无法或不想同步遍历而又需要防止并发线程之间的干扰时很有用。
请注意警告:如果您无法/不会同步并且没有频繁的作者,请使用此选项。 如果您很少写入列表,这是一个可行的选择。
因此,当我们向allocatedNodesOnHost
映射添加新列表时,我们allocatedNodesOnHost
像这样进行操作:
allocatedNodesOnHost.put(hostName, new CopyOnWriteArrayList<>());
然后,当我们访问allocatedNodesOnHost
时,可以删除同步:
public MyNode postNodeOnNFFG(MyNode node, @PathParam("id") String id) {
allocatedNodesOnHost.get(id).add(node.ID);
}
public MyNodes getNodeFromNFFG(@PathParam("id") String id) {
for(String S : allocatedNodesOnHost.get(id)) {
...
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.