class Notification(val context: Context, title: String, message: String) {
private val channelID = "TestMessages"
companion object ID {
var s_notificationID = -1
}
init {
var notificationID = -1
synchronized(s_notificationID) {
if (++s_notificationID == 0)
createNotificationChannel()
notificationID = s_notificationID
}
The above is being called simultaneously from two threads. A breakpoint in createNotificationChannel()
clearly showed that sometimes s_notificationID
equals 1.
However, if I change
synchronized(s_notificationID)
to synchronized(ID)
then it seems to lock fine.
Is synchronized() not locking basic types? And if so, why does it compile?
A look at the generated JVM bytecode indicates that the ID
example looks like
synchronized(ID) { ... }
which is what you'd expect. However, the s_notificationID
example looks more like
synchronized(Integer.valueOf(s_notificationID)) { ... }
In Java, we can only synchronize on objects, not on primitives. Kotlin mostly removes this distinction, but it looks like you've found one place where the implementation still seeps through. Since s_notificationID
is an int
as far as the JVM is concerned (hence, not an object) but synchronized
expects an object, Kotlin is "smart" enough to wrap the value in Integer.valueOf
on demand. Unfortunately for you, that produces wildly inconsistent results, because
This method will always cache values in the range -128 to 127, inclusive, and may cache other values outside of this range.
So for small numbers, this is guaranteed to lock on some cached object in memory that you don't control. For large ones, it may be a fresh object (hence always unlocked) or it might again end up on a cached object out of your hands.
The lesson here, it seems, is: Don't synchronize on primitive types.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With