You are right ... the exact behavior of a bunch of running threads is unpredictable. That's just part of the fun.
See Thread in the JavaDoc and follow a link to Thread.State. The JVM executes instructions on threads that are RUNNABLE. If there are several RUNNABLE threads, it uses some form of "time slicing" to run a few instructions from one, a few from another, then a few from another. Even if a thread remains RUNNABLE all kinds of things going on in the computer can influence how often it gets the CPU and for how long.
Does that answer any questions? Raise any new ones?
A good question is never answered. It is not a bolt to be tightened into place but a seed to be planted and to bear more seed toward the hope of greening the landscape of the idea. John Ciardi
Just to emphasize, when Stan says "run a few instructions", that means something different to the computer than to you. you might see
system.out.println("this is one line of code");
but to the machine, that's many instructions. the JVM sees no problem with printing 3 characters, then stopping and moving to another thread, doing something there (including printing a few characters for that thread)... then coming back and printing a few more characters in this thread.
There are only two hard things in computer science: cache invalidation, naming things, and off-by-one errors
All the above responses saying you cannot predict the order in which different threads will run their tasks are of course true. However, this does not mean that you cannot have any control over the order of execution of code in different threads; it's just that you have to write code explicitly to ensure it.
The wait() and notify[All]() methods are particularly useful in allowing different threads to co-operate. For instance, one thread that needs a result being prepared by another thread can wait() until that other thread calls notify(), to say it has generated the result.
Betty Rubble? Well, I would go with Betty... but I'd be thinking of Wilma.