LateNightDev
LateNightDev

Reputation: 225

Kotlin Concurrency with ConcurrentHashMap while retrieving and removing without using locks

I am trying to understand ConcurrentHashMap and see if I can leverage it w/o adding any locks on my side. I have a ConcurrentHashMap with number of books at the beginning of a day.

class Z {
    val books: ConcurrentHashMap<String, Int> = ConcurrentHashMap()
    
    fun addBook(book: String, numberOfBooks: Int) {
        books[book] = numberOfBooks
    }
 
    fun doSomething(book: String) {
        val numberOfBooks = books.remove(book)
    }
}

The above would be threadsafe. But now if I need to add in a validation just to be sure the book isn't being added twice during initialization I need to add a synchronized block just to be sure that I am not adding something like below.

class Z {
    val books: ConcurrentHashMap<String, Int> = ConcurrentHashMap()
    val lock = Any()
    fun addBook(book: String, numberOfBooks: Int) {
        synchronized(lock) {
            val existingBook = books[book]
            if(existingBook!=null)
                println("Book exists, BEWARE!!")
            books[book] = numberOfBooks
        }
    }
 
    fun doSomething(book: String) {
        var numberOfBooks : Int? 
        synchronized(lock)
            numberOfBooks=books.remove(book)
    }
}

Is there a better way for me to do this. I hate adding a synchronized block just to put in a log statement in there.

Upvotes: 8

Views: 30871

Answers (1)

gidds
gidds

Reputation: 18627

You're in luck: the Map interface has several methods which can help to do this sort of thing atomically, and so are thread-safe in ConcurrentHashMap.

In this case, you don't even need anything exotic, just an explicit call to the put() method, as it returns the previous value (or null if there wasn't one).

So you could simply tweak your first example to have:

fun addBook(book: String, numberOfBooks: Int) {
    val existingNumber = books.put(book, numberOfBooks)
    if (existingNumber != null)
        println("Book exists, BEWARE!!")
}

…which does what you want without any synchronisation.  (You lose Kotlin's array-access syntactic sugar, but that's clearly outweighed here.)

Other methods which can do complex things atomically are compute(), merge(), putIfAbsent(), replace(), and many more; it's worth taking a look through the class so you can recognise situations where they'd come in useful.  As you clearly know, synchronization can bring a hefty performance penalty, so if you can avoid it without sacrificing safety, it can be a big win.

Upvotes: 11

Related Questions