简体   繁体   English

Java和多线程中的Singleton服务

[英]Singleton service in Java and multithreading

I'm developing a Java service using JAX-RS API. 我正在使用JAX-RS API开发Java服务。 I've decided to follow a singleton pattern for it, but now I've some doubts about how to manage concurrency. 我已经决定采用单例模式,但是现在我对如何管理并发性有所怀疑。

The following is a simplified example of my code: 以下是我的代码的简化示例:

@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)) { 
    ... 
    } 
  } 
} 

Do you think that this approach could work? 您认为这种方法可行吗? All the GET requests should happen simultaneously, while the POST requests should be serialized. 所有GET请求应同时发生,而POST请求应被序列化。 Is it correct? 这是正确的吗?

There are a few things that might not work as intended here. 有些事情可能无法按预期工作。 First, since you are declaring your NfvDeployer as a @Singleton , you don't strictly have to make all of your fields static. 首先,因为你可以声明你NfvDeployer@Singleton ,你不必严格,使所有领域的静态。 Since the framework will ensure that only one of these exists, that should cover it. 由于该框架将确保其中只有一个存在,因此应将其涵盖在内。

With regard to synchronization, there are problems with this as-is. 关于同步,按原样存在问题。 Consider the case when you are writing to the List contained as a value in allocatedNodesOnHost and iterating over that same list at the same time. 请考虑以下情况:写入列表中包含为allocatedNodesOnHost的值的列表,并同时遍历同一列表。 You will end up getting a ConcurrentModificationException . 您最终将得到ConcurrentModificationException From the Javadoc : Javadoc

For example, it is not generally permissible for one thread to modify a Collection while another thread is iterating over it. 例如,通常不允许一个线程修改Collection而另一个线程对其进行迭代。

There are at least two solutions to this, and they depend on what kind of guarantees you want. 至少有两种解决方案,它们取决于您想要哪种担保。

Option 1: Consistent View of Data 选项1:一致的数据视图

Suppose the readers of the list and the writers to the list must have a consistent view of the list. 假设列表的读者和列表的作者必须具有一致的列表视图。 Meaning: if somebody is writing a new value it is not ok for the readers to view a slightly older version of the values contained in the list. 含义:如果有人写一个新值是正常的读者查看列表中包含的值的稍旧的版本。 To account for this, we should synchronize all access to the map (and its list values): 为了解决这个问题,我们应该同步对地图的所有访问(及其列表值):

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)) { 
            ... 
        } 
    }
} 

You'll notice here that I'm synchronizing on allocatedNodesOnHost rather than this . 您会在这里注意到,我是在allocatedNodesOnHost而不是this上同步。 By synchronizing on this , we have a very coarse grained lock which affects any other synchronization block we might set up. 通过this同步,我们获得了非常粗糙的锁,它会影响我们可能设置的任何其他同步块。 And again, to avoid ConcurrentModificationExceptions, all access to allocatedNodesOnHost needs to be synchronized. 同样,为了避免ConcurrentModificationExceptions,对allocatedNodesOnHost NodesOnHost的所有访问都必须同步。

Option 2: Inconsistent view of data 选项2:数据视图不一致

When I say "inconsistent", I don't mean wrong, I mean that it might be slightly out of date, and only in the case of concurrent modification. 当我说“不一致”时,我并不是说错误,而是意味着它可能已经过时,仅在并发修改的情况下。 Essentially, we offload the synchronization we do in Option 1 to structures that the JVM provides that will do it for us. 本质上,我们将在选项1中执行的同步卸载到JVM提供的将为我们完成的结构上。

First, we'll declare our field using a ConcurrentHashMap : 首先,我们将使用ConcurrentHashMap声明字段:

private Map<String, List<String>> allocatedNodesOnHost = new ConcurrentHashMap<>();

A ConcurrentHashMap provides us with a map that allows us to read from and write to a map without having to synchronize it. ConcurrentHashMap为我们提供了一个地图,使我们可以在不进行同步的情况下读取和写入地图。 It avoids blocking where it can, so it's good to use this in a multithreaded application when you know you will have concurrent readers and writers. 它避免了在可能的地方阻塞,因此,当您知道将有并发的读取器和写入器时,最好在多线程应用程序中使用它。

And when we create a value in that map, we will use a CopyOnWriteArrayList , which is described as: 当我们在该映射中创建一个值时,将使用一个CopyOnWriteArrayList ,它被描述为:

A thread-safe variant of ArrayList in which all mutative operations (add, set, and so on) are implemented by making a fresh copy of the underlying array. ArrayList的线程安全变体,其中所有可变操作(添加,设置等)都通过对基础数组进行全新复制来实现。 This is ordinarily too costly, but may be more efficient than alternatives when traversal operations vastly outnumber mutations, and is useful when you cannot or don't want to synchronize traversals, yet need to preclude interference among concurrent threads. 通常这样做的成本太高,但是在遍历操作的数量远远超过变异的情况下,它可能比替代方法更有效,并且在您无法或不想同步遍历而又需要防止并发线程之间的干扰时很有用。

Note the warning: go with this option if you can't/won't synchronize and don't have frequent writers. 请注意警告:如果您无法/不会同步并且没有频繁的作者,请使用此选项。 If you have infrequent writes to a list, this is a viable option. 如果您很少写入列表,这是一个可行的选择。

So when we add a new list to our allocatedNodesOnHost map, we do it like this: 因此,当我们向allocatedNodesOnHost映射添加新列表时,我们allocatedNodesOnHost像这样进行操作:

allocatedNodesOnHost.put(hostName, new CopyOnWriteArrayList<>());

And then we can drop synchronization when we access allocatedNodesOnHost : 然后,当我们访问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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM