Search code examples
multithreadinglistkotlinconcurrencyconcurrentmodification

How to compare a previous list and updated a field in multi thread


I have a local cache where I store the runner's lap info, I need to show if the runner's current lap was better or worse than the current lap, while displaying the current lap information.

data class RunInfo(
    val runnerId: String,
    val lapTime: Double,
    var betterThanLastLap: BETTERTHANLASTLAP
)

enum class BETTERTHANLASTLAP {
    NA, YES, NO
}

object RunDB {

    private var listOfRunners: MutableList<RunInfo> =
        java.util.Collections.synchronizedList(mutableListOf())
    private var previousList: MutableList<RunInfo> = mutableListOf()

    fun save(runList: MutableList<RunInfo>) {
        previousList = listOfRunners.toMutableList()
        listOfRunners.clear()
        listOfRunners.addAll(runList)
        listOfRunners.forEach { runner ->
            previousList.forEach { previousLap ->
                if (runner.runnerId == previousLap.runnerId) {
                    runner.betterThanLastLap =
                        when {
                            previousLap.lapTime == 0.0 -> BETTERTHANLASTLAP.NA
                            runner.lapTime >= previousLap.lapTime -> BETTERTHANLASTLAP.YES
                            else -> BETTERTHANLASTLAP.NO
                        }
                }
            }
        }
    }
}

This seems to do the job, but often I get concurrent modification exception. Is there a better way of solving this problem?


Solution

  • I don't recommend combining mutable lists with read-write var properties. Making it mutable in two different ways creates ambiguity and is error prone. Since you're just clearing and replacing the list contents, I would make it a read-only list and a read-write property.

    You need to synchronize the whole function so it can only be executed once at a time.

    object RunDB {
    
        private var listOfRunners: List<RunInfo> = listOf()
        private var previousList: List<RunInfo> = listOf()
    
        fun save(runList: List<RunInfo>) {
            sychronized(this) {
                previousList = listOfRunners.toList()
                listOfRunners = runList.toList()
                listOfRunners.forEach { runner ->
                    previousList.forEach { previousLap ->
                        if (runner.runnerId == previousLap.runnerId) {
                            runner.betterThanLastLap =
                                when {
                                    previousLap.lapTime == 0.0 -> BETTERTHANLASTLAP.NA
                                    runner.lapTime >= previousLap.lapTime -> BETTERTHANLASTLAP.YES
                                    else -> BETTERTHANLASTLAP.NO
                                }
                        }
                    }
                }
            }
        }
    }
    

    It also feels error prone to have a mutable data class in these lists that you're copying and shuffling around. I recommend making it immutable:

    data class RunInfo(
        val runnerId: String,
        val lapTime: Double,
        val betterThanLastLap: BETTERTHANLASTLAP
    )
    
    object RunDB {
    
        private var listOfRunners: List<RunInfo> = listOf()
        private var previousList: List<RunInfo> = listOf()
    
        fun save(runList: List<RunInfo>) {
            sychronized(this) {
                previousList = listOfRunners.toList()
                listOfRunners = runList.map { runner ->
                    val previousLap = previousList.find { runner.runnerId == previousLap.runnerId }
                    runner.copy(betterThanLastLap = when {
                        previousLap == null || previousLap.lapTime == 0.0 -> BETTERTHANLASTLAP.NA
                        runner.lapTime >= previousLap.lapTime -> BETTERTHANLASTLAP.YES
                        else -> BETTERTHANLASTLAP.NO
                    })
                }
            }
        }
    }