Search code examples
javacollectionshashmapthread-safety

Concurrent Map with one key and multiple values


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.


Solution

  • 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]