简体   繁体   中英

Thread Safe without Synchronize over Mutable State

I have this class with 3 read methods and 1 write method:

class ResourceClass {

  private static Map resourceMap = new HashMap();

  // New method to update resource
  public void write(String key, Object resource) {
    resourceMap.put(key, resource);
  }

  public Object read(String var1) {
    return resourceMap.get(var1);
  }

  public Object read(String var1, String var2) {
    // .. Do task with var1 and var2
    return resourceMap.get(var1);
  }

  public Object read(String var1, String var2, String var3) {
    // .. Do task with var1, var2 and var3
    return resourceMap.get(var1);
  }  
}

Currently, this class only contains a write and 3 read methods to consume the static resource. The problem with this configuration is that the only way to update the resourceMap is to restart the application so the ResourceClass is created again the resourceMap is added to the class for its consumption.

What I want is to add some dynamic way to update resourceMap without restarting the service, but for that I have to make this class Thread-Safe, in order to handle a write method to update the resourceMap safely. For this I have the option to use synchronized keyword in read and write methods so only one thread has access to resourceMap . This approach solves the problem, but includes others as well. These read methods are high-concurrent methods so adding a synchronized keyword will impact the service performance dramatically and surely we don't want that.

Does any body knows a way to keep the threads reading (not blocking each other) but when there comes one thread to write all read methods wait for the write to finish and resume when the write finishes?

As @Mike Mnomonic said in the comments, ConcurrentHashMap is a thread-safe map with a tunable concurrency level. As with other hash maps, ConcurrentHashMap has a backing array; this backing array is split into several sub-arrays depending on your specified concurrency level (default 16) with one lock per sub-array, for example if you have a capacity of 128 and are using the default concurrency level of 16 then sub-array [0,8) has its own lock, [8, 16) has its own lock, [16, 24) has its own lock, and so forth, so two threads can write to two different sub-arrays without blocking each other.

If writes are very infrequent then you may get better performance with an ImmutableMap wrapped in an AtomicReference .

private final AtomicReference<ImmutableMap<String, Object>> resourceMap;

public void write(String key, Object value) {
    boolean success = false;
    while(!success) {
        ImmutableMap oldMap = resourceMap;
        ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();
        builder.putAll(resourceMap.entrySet());
        builder.put(key, value);
        success = resourceMap.compareAndSet(oldMap, builder.build());
    }
}

public Object read(String var1) {
    ...
    return resourceMap.get().get(var1); // get map, then get value
}
public Object read(String var1, String var2);
public Object read(String var1, String var2, String var3);

On a write you're copying the old map, adding the new value, and swapping the new map for the old map; if two updates try to stomp on each other then only one will succeed, meanwhile the other will keep retrying until its update sticks. The readers don't need to care about any of this - they just get the current map.

For highly read heavy loads, consider cloning the collection and adding to the clone and then assigning the result to the field.

If the write methods may be called concurrently, work may have to be done to ensure that access to this method is 'thread safe'.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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