0

I am trying to reproduce a old-school solution in Kotlin to classic Consumer-Producer problem with multiple threads and shared memory space. In Java I would use synchronized methods to access a shared space. In Kotlin, however, it seems that @Synchronized annotated method is throwing IllegalMonitorStateException. I was expecting that annotated methods should behave exactly as they do in Java but it seems that is not the case. I solved the problem with synchronized(){} function but I'm still puzzled that @Synchronized doesn't work. Why is that?

In the following code, Producer "produces" a new value by incrementing a counter (Long) inside SynchronizedBox and Consumer reads that value then prints it to console.

Kotlin MessageBox that does not work

package concurrency.producer_consumer

class MessageBox(var message: Long = 0): SynchronizedBox {
    private val lock = Object()
    private var empty = true

    @Synchronized
    override fun syncIncrement() {
        while (!empty) {
            lock.wait()
        }

        message++
        empty = false
        lock.notifyAll()
    }

    @Synchronized
    override fun readValue(): Long {
          while (empty) {
              lock.wait()
          }

          val readValue = message
          empty = true
          lock.notifyAll()

          return readValue
    }
}

Java variant that works:

package concurrency.producer_consumer;

public class JBox implements SynchronizedBox {
    private long value = 0;
    private boolean empty = true;

    @Override
    public synchronized void syncIncrement() {
        while (!empty) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }

        value++;
        empty = false;
        notifyAll();
    }

    @Override
    public synchronized long readValue() {
        while (empty) {
            try {
                wait();
            } catch (InterruptedException e) {}
        }

        empty = true;
        return value;
    }
}

Kotlin version that actually works:

package concurrency.producer_consumer

class MessageBox(var message: Long = 0): SynchronizedBox {
    private val lock = Object()
    private var empty = true

    override fun syncIncrement() {
        synchronized(lock) {
            while (!empty) {
                lock.wait()
            }

            message++
            empty = false
            lock.notifyAll()
        }
    }

    override fun readValue(): Long {
        synchronized(lock) {
            while (empty) {
                lock.wait()
            }

            empty = true
            lock.notifyAll()
            return message
        }
    }
}

Rest of the code:

Consumer: package concurrency.producer_consumer

class Consumer(private val messageBox: SynchronizedBox): Runnable {

    override fun run() {
        println("consumer thread: ${Thread.currentThread().id}: started")

        while (true) {
            println("consumer: ${messageBox.readValue()}")
            Thread.sleep(1_000)
        }
    }
}

Producer:

class Producer(private val messageBox: SynchronizedBox): Runnable {

    override fun run() {
        println("producer thread: ${Thread.currentThread().id}: started")

        while (true) {
            messageBox.syncIncrement()
            Thread.sleep(1_000)
        }
    }
}

Interface

package concurrency.producer_consumer

interface SynchronizedBox {
    fun syncIncrement()
    fun readValue(): Long
}

Launcher

package concurrency.producer_consumer

fun main() {
    val box: SynchronizedBox = MessageBox()
    val producer1 = Producer(box)
    val consumer = Consumer(box)

    val threadP1 = Thread(producer1)
    val threadC = Thread(consumer)

    threadP1.start()
    threadC.start()
}
shadox
  • 3,238
  • 4
  • 24
  • 38
  • 2
    Is this getting confused about the lock objects?  In the non-working Kotlin version, `@Synchronized` locks on the `MessageBox` instance, but it's `wait()`ing on and `notify()`ing the `lock` object instead.  This is unlike the Java version, which does everything on the `MessageBox` instances, and unlike the working Kotlin version, which does everything on the `lock` object.  (`@Synchronized` should work exactly as in Java: see https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/-synchronized/) – gidds May 14 '19 at 10:25
  • @gidds so how do I `wait` and `notify` on `MessageBox` instead, in non-working case? I know that MessageBox does not inherit Object's `wait` and `notify` methods and even if I inherit directly from Object it does not work (I've tried). – shadox May 14 '19 at 12:21
  • 2
    Kotlin's designers were probably trying to discourage using the low-level monitors at all, as they're fiddly and error-prone.  There are a couple of ideas in this answer: https://stackoverflow.com/a/44589962/10134209.  But I'd recommend seeing whether you can use a higher-level abstraction, such as a BlockingQueue, or the Executor framework, or kotlin.concurrent classes, or actors — many people have been down this road before! – gidds May 14 '19 at 12:40
  • I'm aware of the alternative but it's not what I am after. I'm trying to understand what is happening behind the scenes so I actually need to figure out how locking and synchronization works in **Kotlin**. – shadox May 14 '19 at 15:53
  • @gidds, you were right and I figured out that I've made few mistakes when extending `Object`. I've posted my solution. – shadox May 15 '19 at 13:03

2 Answers2

0

It seems that indeed @Synchronized locks on MessageBox and it doesn't work only because in Kotlin everything extends Any instead of Java's Object. Although it is not recommended way of doing concurrent programming in Kotlin, the solution is to simply extend Object as shown below in MessageBoxLock:

package concurrency.producer_consumer.message_box

@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
class MessageBoxLock(var message: Long = 0): Object(), SynchronizedBox {

    private var empty = true

    @Synchronized
    override fun syncIncrement() {
        while (!empty) {
            wait()
        }

        message++
        empty = false
        notifyAll()
    }

    @Synchronized
    override fun readValue(): Long {

        while (empty) {
            wait()
        }

        empty = true
        notifyAll()

        return message
    }
}
shadox
  • 3,238
  • 4
  • 24
  • 38
0

I had similar problem, so instead of annotating with @Synchronized keyword, I have changed to synchronized call as shown below, and it worked for me.

private fun produce() = synchronized(lock){
   ...
}

Kotlin code example to look through:

fun main() {
    val producer = Producer()
    producer.name = "PRODUCER-THREAD"
    producer.start()
    val consumer = Consumer(producer)
    consumer.name = "CONSUMER-THREAD"
    consumer.start()
}

class Consumer(private val producer: Producer) : Thread() {
    override fun run() {
        try {
            while (true) {
                val data = producer.consume()
                println("$data consumed by: ${currentThread().name}")
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}

class Producer : Thread() {
    override fun run() {
        try {
            while (true) produce()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    private fun produce() = synchronized(lock) {
        while (messages.size == MAX_SIZE) lock.wait()

        val data = LocalDateTime.now().toString()
        messages.add(data)
        println("Data produced")
        lock.notify()
    }

    fun consume(): String = synchronized(lock) {
        lock.notify()
        while (messages.isEmpty()) lock.wait()

        val data = messages[0]
        println("Data consumed as: $data")
        messages.remove(data)
        return data
    }

    companion object {
        const val MAX_SIZE = 3
        val lock = Object()
        val messages = arrayListOf<String>()
    }
}
Ercan
  • 2,601
  • 22
  • 23