Concurrent Map with one key and multiple values

Viewed 704

I need to have a Map which is thread safe and can have multiple values. I did write one using MultiMap as

    private MultiMap<String, String> uploadIdETagMap = new MultiValueMap<String, String>();

//Function to get tag value and add to map to that uploadId key
    public void uploadPartForMultiPartUpload() {
        try {
            UploadPartRequest partRequest = new UploadPartRequest();
            partRequest = partRequest.withBucketName(bucketName).withInputStream(inputStream).withKey(fileName)
                    .withPartNumber(partNumber).withUploadId(uploadId).withPartSize(sizeOfData)
                    .withLastPart(isLastPart);
            UploadPartResult result = s3Client.uploadPart(partRequest);
            uploadIdETagMap.put(uploadId, result.getPartETag());
        } catch (Exception e) {
            log.error("Error uploading part: {} in multi part upload.", partNumber, e);
        }
    }

    List<String> tagList = (List<String>) uploadIdETagMap.get(uploadId);

So everytime i will get new tag value for a particular uplaodId key and i need all of those tags at the end and there will be other uploads processing same time.

In the above code, tag gets added every time as the value of the MultiMap for specific uploadId key. The above code works, but both MultiMap and MultiValueMap is deprecated and not thread safe.

So any suggestion how can i achieve this with latest version and thread safety.

2 Answers

tl;dr

personFavoriteColors.computeIfAbsent( 
    "Alice" , 
    ( x -> new CopyOnWriteArrayList<>() )
)
.add( "Purple" )
;

personFavoriteColors.computeIfAbsent( 
    "Alice" , 
    ( x -> new CopyOnWriteArrayList<>() )
)
.add( "Gold" )
;

personFavoriteColors.toString(): [Purple, Gold]

Map::computeIfAbsent

Java now has multimap behavior built-in with new computeIfAbsent method defined on the Map interface.

Define your map.

ConcurrentMap< String , Set< String> > personFavoriteColors = new ConcurrentSkipListMap<>() ;

We are using a Map implementation that implements the ConcurrentMap interface for thread-safety as you requested. Java offers two such implementations.

Table of map implementations in Java 11, comparing their features

Use a single line to create a new list or set to hold the multiple values.

personFavoriteColors.computeIfAbsent( 
    "Alice" , 
    ( x -> new CopyOnWriteArraySet<>() )   // Lambda expression. Run only if needed to run (if absent). 
)                                          // Returns the value of a map (a `Set` or `List` when desiring a multimap). 
.add( "Purple" )
;

You may access the Set (or List) outside of the Map that owns it. So that Set (or List) must also be concurrent.

Here we used a concurrent set, CopyOnWriteArraySet as our value for the map.

For example:

Set< String > favoriteColorsForAlice = personFavoriteColors.get( "Alice" ) ;
System.out.println( favoriteColorsForAlice ) ; 

See this code run live on IdeOne.com.

[Purple, Gold]

For whatever Map<String,List<String>> implementation you use you could do the following:

String key;
String value;
map.compute(key, ((k,v)-> v == null ? new ArrayList<>() : v).add(value));

The above checks to see if the List<String> for key exists, if not it creates one, otherwise, it uses the existing one. In both cases, the value is added to the new or existing list for a given key.

Related