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()
}