I'm running this code in Scala:
def injectFunction(body: =>Unit): Thread = {
val t = new Thread {
override def run() = body
}
t
}
private var counter: Int = 0
def increaseCounter(): Unit = this.synchronized {
//putting this as synchronized doesn't work for some reason..
counter = counter 1
counter
}
def printCounter(): Unit = {
println(counter)
}
val t1: Thread = injectFunction(increaseCounter())
val t2: Thread = injectFunction(increaseCounter())
val t3: Thread = injectFunction(printCounter())
t1.start()
t2.start()
t3.start()
This prints out 1 most of the time, though sometimes 2 and 0 a few times. Shouldn't the this.synchronized before the increaseCounter() method ensure that it's thread safe, and print 2 every time? I've also tried adding this.synchronized in the definition of printCounter(), with no luck.
CodePudding user response:
Very entertaining example! It's so broken that it actually fails at failing to fail:
It was already failing right from the beginning, because it started with the wrong assumption that the
synchronizedkeyword would somehow force thet3to execute last. But that's simply not the case, thesynchronizedhas nothing to do with the order in which the threads are executed. The threads can still run in arbitrary order, thesynchronizedmerely ensures that they don't enter thesynchronizedblock simultaneously.Then it additionally fails at generating a random
0/1/2output, because there is nosynchronizedblock around thecounterin theprintln, i.e. there is no actual guarantee that the printing thread will see the random changes made by the two other threads. As it is, the printing thread had every right to print0, without being obliged to do so.The statements that are executed in the initializer of the
objectattempt to acquirethisinthis.synchronized. If one additionally addstN.join()into the initializer, everything freezes with a deadlock (because the initialization cannot finish beforet1,t2,t3canjoin, and they cannotjoinbefore they can enter athis.synchronized, and they cannot enterthis.synchronizedbefore the initialization has unlockedthis).
Here is the example that fixes all three problems:
object Example {
def injectFunction(body: =>Unit): Thread = {
val t = new Thread {
override def run() = body
}
t
}
private var counter: Int = 0
def increaseCounter(): Unit = {
//
Thread.sleep(util.Random.nextInt(1000))
this.synchronized {
counter = 1
}
}
def printCounter(): Unit = {
Thread.sleep(util.Random.nextInt(1000))
println("Random state: " this.synchronized { counter })
}
def main(args: Array[String]): Unit = {
val t1: Thread = injectFunction(increaseCounter())
val t2: Thread = injectFunction(increaseCounter())
val t3: Thread = injectFunction(printCounter())
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
println("Final state: " counter)
}
}
Now this will indeed randomly output
Random state: 0
Final state: 2
or
Random state: 1
Final state: 2
or
Random state: 2
Final state: 2
The crucial differences to your code are:
- random
sleeps make the parallelism at least somewhat visible. Without this, you might get2&2repeatedly. - the reading access to
counterinprintln(counter)must also be synchronized - if you want to demonstrate that the final state of the counter is
2, you have to wait for all three threads tojoin. - All the statements are in the
main, so that theobjectcan be initialized correctly.
