Everything is synchronized and locked. That means you don't need to cover the intricate details of what happens when you access a field from multiple threads without Happens-Before relationships established.
That makes this not really an exercise in threading. That's good - generally these questions are annoying, in the sense that the useful thing to know is merely: "This code is wrong, because it violates JMM here and here, and this is how you do it right". Knowing what possible answers the code could give is esoteric knowledge that is utterly ridiculous to expect anybody to know - it's not useful information for the job, at any rate. You might as well fail someone from the interview because their fly-fishing knowledge isn't up to par. So, rejoice in that, I guess. It's a fair question.
This question boils down to:
- We start with 0
- We have 10x '+1' operations (increment).
- We have 10x '*2' operations (double).
- We cannot perform a 'double' operation unless that many increments have already been performed.
What's the MIN and MAX, respectively?
The answer seems fairly simple, and is a math/logic question, it has nothing to do with programming (some basic java knowledge is required to break down the problem to this, then logic is needed to figure out the answer).
You get the highest value by maximizing the impact of *2
. Hence, by running the add operations first - that gets you to 9. Then double that 10 times, gets you to 9216.
You get the lowest by minimizing. Hence, 2046: Incr-double-incr-double - in that order.
It would be quite difficult to actually make a system give you 9216. Probably impossible. The point is, if a JVM prints 9216, it is working fine - the JMM (java memory model, a chapter in the Java Language Spec) gives a JVM the right to do that.
If you want to play around with it, get rid of the threading and replace the logic with random chance instead.
I think the minimum answer is 2.
This cannot possibly be the case even if you play around with this code. For example, if you just remove all locking, the actual answer is 0 - NONE of the 10 threads are guaranteed to propagate their changes to the main thread, so the main thread is free to print 0 in that case. Any individual thread would see a minimum of 2 (and is likely to on modern JVM impls), but those worker threads aren't doing the printing.
//todo add catch exception
I assume you wrote that? I would make a note on your file 'programmer has no idea how to do exception management properly'. You're in some luck - a lot of java programmers are completely clueless on this, but you're still getting past me only on the basis that hiring devs is hard right now so I need to be less picky.
InterruptedException
does not occur unless you explicitly write code that interrupts. Given that you don't, and given that this is main
, where you can, and should, and even did, just pile your checked exceptions into the throws
line (or even better, write throws Exception
) you're doing exactly what you should be doing. So, that note indicating you feel guilt about having done the right thing and evidently feel the proper way to write this exercise would have been to add a catch block there, indicates you do not actually know what you are doing. It's not really possible to know what the reviewers were thinking, but it's possible that threw that off a bit.
Or they are also clueless, and sticklers, and downmarked you for not doing it the (wrong) way they wanted, and write a catch block.
Here are a few rules of thumb to help with exception handling. Remember: Rules of thumb. That means they usually hold, but not always.
If you don't know what to do in an exception block, do not write one, or if you're forced into writing one (due to a API design rule (see below) or because you're overriding and can't declare it in your throws
), then you MUST wrap it. Do not, ever, just 'log it' or 'print it' and carry on. Yes, every tutorial under the sun and many IDEs default to it. That just means many tutorials and many IDEs are selectively braindead in this regard. Yes, for most exceptions, you wouldn't know what to do, and therefore this rule kicks in.
If, looking at solely the signature of the method (the name, param types, and the class it is in - i.e. not at its code) it is rather obvious that some checked exception could occur, then that exception should be in the throws clause. e.g. a method named queryPerson
that does not throws SQLException
is virtually guaranteed to be badly written. However, the flipside is also true: e.g. a method called save()
in class GameState
should only throws SQLException
if 'this system stores stuff in the database' is inherent to the design of the GameState
class. If it is not, write your own exception and wrap your SQLException in it.
If a checked exception cannot possibly even occur (Example: the IOException
caused by stream.read()
even when you know that stream is in fact a ByteArrayInputStream
that cannot possibly throw it, or, new String(byteArray, "UTF-8")
- UTF_8 is guaranteed by the JVM spec so that cannot possibly throw the 'I do not know that charset encoding' exception) - then the API is badly designed. Hence, do not write such APIs and if you're forced into using one, make sure to check the docs, there's often a better alternative. For example, new String(byteArray, StandardCharsets.UTF_8)
is correct; it avoids having to deal with an impossible checked exception.
When designing APIs, if some exceptional state seems virtually never 'handleable' by all use cases of your library/method you can think of, it should be unchecked. Similarly, if it is likely that some exceptional state can never occur and the caller knows it, it should be unchecked. Otherwise, make it checked.