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
synchronized
keyword would somehow force thet3
to execute last. But that's simply not the case, thesynchronized
has nothing to do with the order in which the threads are executed. The threads can still run in arbitrary order, thesynchronized
merely ensures that they don't enter thesynchronized
block simultaneously.Then it additionally fails at generating a random
0
/1
/2
output, because there is nosynchronized
block around thecounter
in 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
object
attempt to acquirethis
inthis.synchronized
. If one additionally addstN.join()
into the initializer, everything freezes with a deadlock (because the initialization cannot finish beforet1
,t2
,t3
canjoin
, and they cannotjoin
before they can enter athis.synchronized
, and they cannot enterthis.synchronized
before 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
sleep
s make the parallelism at least somewhat visible. Without this, you might get2
&2
repeatedly. - the reading access to
counter
inprintln(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 theobject
can be initialized correctly.