by Charles L. Perkins
Today, you'll learn more about the threads mentioned briefly in Week 2:
First, let's begin by motivating the need for threads.
Threads are a relatively recent invention in the computer science world. Although processes, their larger parent, have been around for decades, threads have only recently been accepted into the mainstream. What's odd about this is that they are extremely valuable, and programs written with them are noticeably better, even to the casual user. In fact, some of the best individual, Herculean efforts over the years have involved implementing a threads-like facility by hand to give a program a more friendly feel to its users.
Imagine that you're using your favorite text editor on a large file. When it starts up, does it need to examine the entire file before it lets you edit? Does it need to make a copy of the file? If the file is huge, this can be a nightmare. Wouldn't it be nicer for it to show you the first page, enabling you to begin editing, and somehow (in the background) complete the slower tasks necessary for initialization? Threads allow exactly this kind of within-the-program parallelism.
Perhaps the best example of threading (or lack of it) is a WWW browser. Can your browser download an indefinite number of files and Web pages at once while still enabling you to continue browsing? While these pages are downloading, can your browser download all the pictures, sounds, and so forth in parallel, interleaving the fast and slow download times of multiple Internet servers? HotJava can do all of these thingsand moreby using the built-in threading of the Java language.
If threading is so wonderful, why doesn't every system have it? Many modern operating systems have the basic primitives needed to create and run threads, but they are missing a key ingredient. The rest of their environment is not thread-safe. Imagine that you are in a thread, one of many, and each of you is sharing some important data managed by the system. If you were managing that data, you could take steps to protect it (as you'll see later today), but the system is managing it. Now visualize a piece of code in the system that reads some crucial value, thinks about it for a while, and then adds 1 to the value:
if (crucialValue > 0) { . . . // think about what to do crucialValue += 1; }
Remember that any number of threads may be calling upon this part of the system at once. The disaster occurs when two threads have both executed the if test before either has incremented the crucialValue. In that case, the value is clobbered by them both with the same crucialValue + 1, and one of the increments has been lost. This may not seem so bad to you, but imagine instead that the crucial value affects the state of the screen as it is being displayed. Now, unfortunate ordering of the threads can cause the screen to be updated incorrectly. In the same way, mouse or keyboard events can be lost, databases can be inaccurately updated, and so forth.
This disaster is inescapable if any significant part of the system has not been written with threads in mind. Therein lies the barrier to a mainstream threaded environmentthe large effort required to rewrite existing libraries for thread safety. Luckily, Java was written from scratch with this is mind, and every Java class in its library is thread-safe. Thus, you now have to worry only about your own synchronization and thread-ordering problems, because you can assume that the Java system will do the right thing.
Getting used to threads takes a little while and a new way of thinking. Rather than imagining that you always know exactly what's happening when you look at a method you've written, you have to ask yourself some additional questions. What will happen if more than one thread calls into this method at the same time? Do you need to protect it in some way? What about your class as a whole? Are you assuming that only one of its methods is running at the same time?
Often you make such assumptions, and a local instance variable will be messed up as a result. Let's make a few mistakes and then try to correct them. First, the simplest case:
public class ThreadCounter { int crucialValue; public void countMe() { crucialValue += 1; } public int howMany() { return crucialValue; } }
This code suffers from the most pure form of the "synchronization problem:" the += takes more than one step, and you may miscount the number of threads as a result. (Don't worry about how threads are created yet, just imagine that a whole bunch of them are able to call countMe(), at once, at slightly different times.) Java allows you to fix this:
public class SafeThreadCounter { int crucialValue; public synchronized void countMe() { crucialValue += 1; } public int howMany() { return crucialValue; } }
The synchronized keyword tells Java to make the block of code in the method thread safe. Only one thread will be allowed inside this method at once, and others have to wait until the currently running thread is finished with it before they can begin running it. This implies that synchronizing a large, long-running method is almost always a bad idea. All your threads would end up stuck at this bottleneck, waiting single file to get their turn at this one slow method.
It's even worse than you might think for unsynchronized variables. Because the compiler can keep them around in registers during computations, and a thread's registers can't be seen by other threads (especially if they're on another processor in a true multiprocessor computer), a variable can be updated in such a way that no possible order of thread updates could have produced the result. This is completely incomprehensible to the programmer. To avoid this bizarre case, you can label a variable volatile, meaning that you know it will be updated asynchronously by multiprocessor-like threads. Java then loads and stores it each time it's needed and does not use registers.
The method howMany() in the last example doesn't need to be synchronized, because it simply returns the current value of an instance variable. Someone higher in the call chain may need to be synchronized, thoughsomeone who uses the value returned from the method. Here's an example:
public class Point { //redefines class Point from package java.awt private float x, y; //OK since we're in a different package here public float x() { // needs no synchronization return x; } public float y() { // ditto return y; } . . . // methods to set and change x and y } public class UnsafePointPrinter { public void print(Point p) { System.out.println("The point's x is " + p.x() + " and y is " + p.y() + "."); } }
The analogous methods to howMany() are x() and y(). They need no synchronization, because they just return the values of instance variables. It is the responsibility of the caller of x() and y() to decide whether it needs to synchronize itselfand in this case, it does. Although the method print() simply reads values and prints them out, it reads two values. This means that there is a chance that some other thread, running between the call to p.x() and the call to p.y(), could have changed the value of x and y stored inside the Point p. Remember, you don't know how many other threads have a way to reach and call methods in this Point object! "Thinking multithreaded" comes down to being careful any time you make an assumption that something has not happened between two parts of your program (even two parts of the same line, or the same expression, such as the string + expression in this example).
You could try to make a safe version of print() by simply adding the synchronized keyword modifier to it, but instead, let's try a slightly different approach:
public class TryAgainPointPrinter { public void print(Point p) { float safeX, safeY; synchronized(this) { safeX = p.x(); // these two lines now safeY = p.y(); // happen atomically } System.out.print("The point's x is " + safeX + " y is " + safeY); } }
The synchronized statement takes an argument that says what object you would like to lock to prevent more than one thread from executing the enclosed block of code at the same time. Here, you use this (the instance itself), which is exactly the object that would have been locked by the synchronized method as a whole if you had changed print() to be like your safe countMe() method. You have an added bonus with this new form of synchronization: you can specify exactly what part of a method needs to be safe, and the rest can be left unsafe.
Notice how you took advantage of this freedom to make the protected part of the method as small as possible, while leaving the String creations, concatenations, and printing (which together take a small but nonzero amount of time) outside the "protected" area. This is both good style (as a guide to the reader of your code) and more efficient, because fewer threads get stuck waiting to get into protected areas.
The astute reader, though, may still be worried by the last example. It seems as if you made sure that no one executes your calls to x() and y() out of order, but have you prevented the Point p from changing out from under you? The answer is no, you still have not solved the problem. You really do need the full power of the synchronized statement:
public class SafePointPrinter { public void print(Point p) { float safeX, safeY; synchronized(p) { // no one can change p safeX = p.x(); // while these two lines safeY = p.y(); // are happening atomically } System.out.print("The point's x is " + safeX + " y is " + safeY); } }
Now you've got it. You actually needed to protect the Point p from changes, so you lock it by giving it as the argument to your synchronized statement. Now when x() and y() happen together, they can be sure to get the current x and y of the Point p, without any other thread being able to call a modifying method between. You're still assuming, however, that the Point p has properly protected itself. (You can always assume this about system classesbut you wrote this Point class.) You can make sure by writing the only method that can change x and y inside p yourself:
public class Point { private float x, y; . . . // the x() and y() methods public synchronized void setXAndY(float newX, float newY) { x = newX; y = newY; } }
By making the only "set" method in Point synchronized, you guarantee that any other thread trying to grab the Point p and change it out from under you has to wait: you've locked the Point p with your synchronized(p) statement, and any other thread has to try to lock the same Point p via the implicit synchronized(this) statement p now executes when entering setXAndY(). Thus, at last, you are thread-safe.
An added benefit of the use of the synchronized modifier on methods (or of synchronized(this) {. . .}) is that only one of these methods (or blocks of code) can run at once. You can use that knowledge to guarantee that only one of several crucial methods in a class will run at once:
public class ReallySafePoint { private float x, y; public synchronized Point getUniquePoint() { return new Point(x, y); // can be a less safe Point } // because only the caller has it public synchronized void setXAndY(float newX, float newY) { x = newX; y = newY; } public synchronized void scale(float scaleX, float scaleY) { x *= scaleX; y *= scaleY; } public synchronized void add(ReallySafePoint aRSP) { Point p = aRSP.getUniquePoint(); x += p.x(); y += p.y(); } // Point p is soon thrown away by GC; no one else ever saw it }
This example combines several of the ideas mentioned previously. To avoid a caller's having to synchronize(p) whenever getting your x and y, you give them a synchronized way to get a unique Point (like returning multiple values). Each method that modifies the object's instance variables is also synchronized to prevent it from running between the x and y references in getUniquePoint() and from stepping on one another as they modify the local x and y. Note that add() itself uses getUniquePoint() to avoid having to say synchronized(aRSP).
Classes that are this safe are a little unusual; it is more often your responsibility to protect yourself from other threads' use of commonly held objects (such as Points). Only when you know for certain that you're the only one that knows about an object, can you fully relax. Of course, if you created the object yourself and gave it to no one else, you can be that certain.
Finally, suppose you want a class variable to collect some information across all a class's instances:
public class StaticCounter { private static int crucialValue; public synchronized void countMe() { crucialValue += 1; } }
Is this safe? If crucialValue were an instance variable, it would be. Because it's a class variable, however, and there is only one copy of it for all instances, you can still have multiple threads modifying it by using different instances of the class. (Remember, the synchronized modifier locks the object thisan instance.) Luckily, you already know the tools you need to solve this:
public class StaticCounter { private static int crucialValue; public void countMe() { synchronized(getClass()) { // can't directly name StaticCounter crucialValue += 1; // the (shared) class is now locked } } }
The trick is to "lock" on a different objectnot on an instance of the class, but on the class itself. Because a class variable is "inside" a class, just as an instance variable is inside an instance, this shouldn't be all that unexpected. In a similar way, classes can provide global resources that any instance (or other class) can access directly by using the class name, and lock by using that same class name. In the last example, crucialValue was used from within an instance of StaticCounter, but if crucialValue were declared public instead, from anywhere in the program, it would be safe to say the following:
synchronized(Class.forName("StaticCounter")) { StaticCounter.crucialValue += 1; }
You can now begin to appreciate how much work the Java team has done for you by thinking all these hard thoughts for each and every class (and method!) in the Java class library.
Now that you understand the power (and the dangers) of having many threads running at once, how are those threads actually created?
Because there is a class java.lang.Thread, you might guess that you could create a thread of your own by subclassing itand you are right:
public class MyFirstThread extends Thread { // a.k.a., java.lang.Thread public void run() { . . . // do something useful } }
You now have a new type of Thread called MyFirstThread, which does something useful (unspecified) when its run() method is called. Of course, no one has created this thread or called its run() method, so it does absolutely nothing at the moment. To actually create and run an instance of your new thread class, you write the following:
MyFirstThread aMFT = new MyFirstThread(); aMFT.start(); // calls our run() method
What could be simpler? You create a new instance of your thread class and then ask it to start running. Whenever you want to stop the thread, you use this:
aMFT.stop();
Besides responding to start() and stop(), a thread can also be temporarily suspended and later resumed:
Thread t = new Thread(); t.suspend(); . . . // do something special while t isn't running t.resume();
A thread will automatically suspend() and then resume() when it's first blocked at a synchronized point and then later unblocked (when it's that thread's "turn" to run).
This is all well and good if every time you want to create a Thread you have the luxury of being able to place it under the Thread class in the single-inheritance class tree. What if it more naturally belongs under some other class, from which it needs to get most of its implementation? The interfaces of Day 16 come to the rescue:
public class MySecondThread extends ImportantClass implements Runnable { public void run() { . . . // do something useful } }
By implementing the interface Runnable, you declare your intention to run in a separate thread. In fact, the class Thread itself implements this interface, as you might expect from the design discussions on Day 16. As you also might guess from the example, the interface Runnable specifies only one method: run(). As in MyFirstThread, you expect someone to create an instance of a thread and somehow call your run() method. Here's how this is accomplished:
MySecondThread aMST = new MySecondThread(); Thread aThread = new Thread(aMST); aThread.start(); // calls our run() method, indirectly
First, you create an instance of MySecondThread. Then, by passing this instance to the constructor making the new Thread, you make it the target of that Thread. Whenever that new Thread starts up, its run() method calls the run() method of the target it was given (assumed by the Thread to be an object that implements the Runnable interface). When start() is called, aThread (indirectly) calls your run() method. You can stop aThread with stop(). If you don't need to talk to the Thread explicitly or to the instance of MySecondThread, here's a one line shortcut:
new Thread(new MySecondThread()).start();
public class SimpleRunnable implements Runnable { public void run() { System.out.println("in thread named '" + Thread.currentThread().getName() + "'"); } // any other methods run() calls are in current thread as well } public class ThreadTester { public static void main(String argv[]) { SimpleRunnable aSR = new SimpleRunnable(); while (true) { Thread t = new Thread(aSR); System.out.println("new Thread() " + (t == null ? "fail" : "succeed") + "ed."); t.start(); try { t.join(); } catch (InterruptedException ignored) { }
// waits for thread to finish its run() method } } }
The class method currentThread() can be called to get the thread in which a method is currently executing. If the SimpleRunnable class were a subclass of Thread, its methods would know the answer already (it is the thread running). Because SimpleRunnable simply implements the interface Runnable, however, and counts on someone else (ThreadTester's main()) to create the thread, its run() method needs another way to get its hands on that thread. Often, you'll be deep inside methods called by your run() method when suddenly you need to get the current thread. The class method shown in the example works, no matter where you are.
The example then calls getName() on the current thread to get the thread's name (usually something helpful, such as Thread-23) so it can tell the world in which thread run() is running. The final thing to note is the use of the method join(), which, when sent to a thread, means "I'm planning to wait forever for you to finish your run() method." You don't want to do this lightly: if you have anything else important you need to get done in your thread any time soon, you can't count on how long the join()ed thread may take to finish. In the example, its run() method is short and finishes quickly, so each loop can safely wait for the previous thread to die before creating the next one. (Of course, in this example, you didn't have anything else you wanted to do while waiting for join() anyway.) Here's the output produced:
new Thread() succeeded. in thread named 'Thread-1' new Thread() succeeded. in thread named 'Thread-2' new Thread() succeeded. in thread named 'Thread-3' ^C
Ctrl+C was pressed to interrupt the program, because it otherwise would continue forever.
If you want your threads to have particular names, you can assign them yourself by using a two-argument form of Thread's constructor:
public class NamedThreadTester { public static void main(String argv[]) { SimpleRunnable aSR = new SimpleRunnable(); for (int i = 1; true; ++i) { Thread t = new Thread(aSR, "" + (100 - i) + " threads on the wall..."); System.out.println("new Thread() " + (t == null ? "fail" : "succeed") + "ed."); t.start(); try { t.join(); } catch (InterruptedException ignored) { } } } }
which takes a target object, as before, and a String, which names the new thread. Here's the output:
new Thread() succeeded. in thread named '99 threads on the wall...' new Thread() succeeded. in thread named '98 threads on the wall...' new Thread() succeeded. in thread named '97 threads on the wall...' ^C
Naming a thread is one easy way to pass it some information. This information flows from the parent thread to its new child. It's also useful, for debugging purposes, to give threads meaningful names (such as network input) so that when they appear during an errorin a stack trace, for exampleyou can easily identify which thread caused the problem. You might also think of using names to help group or organize your threads, but Java actually provides you with a ThreadGroup class to perform this function. A ThreadGroup allows you to group threads, to control them all as a unit, and to keep them from being able to affect other threads (useful for security).
Let's imagine a different version of the last example, one that creates a thread and then hands the thread off to other parts of the program. Suppose it would then like to know when that thread dies so that it can perform some cleanup operation. If SimpleRunnable were a subclass of Thread, you might try to catch stop() whenever it's sentbut look at Thread's declaration of the stop() method:
public final void stop() { . . . }
The final here means that you can't override this method in a subclass. In any case, SimpleRunnable is not a subclass of Thread, so how can this imagined example possibly catch the death of its thread? The answer is to use the following magic:
public class SingleThreadTester { public static void main(String argv[]) { Thread t = new Thread(new SimpleRunnable()); try { t.start(); someMethodThatMightStopTheThread(t); } catch (ThreadDeath aTD) { . . . // do some required cleanup throw aTD; // re-throw the error } } }
You understand most of this magic from yesterday's lesson. All you need to know is that if the thread created in the example dies, it throws an error of class ThreadDeath. The code catches that error and performs the required cleanup. It then rethrows the error, allowing the thread to die. The cleanup code is not called if the thread exits normally (its run() method completes), but that's fine; you posited that the cleanup was needed only when stop() was used on the thread.
You might wonder exactly what order your threads will be run in, and how you can control that order. Unfortunately, the current implementations of the Java system cannot precisely answer the former, though with a lot of work, you can always do the latter.
The part of the system that decides the real-time ordering of threads is called the scheduler.
Normally, any scheduler has two fundamentally different ways of looking at its job: non-preemptive scheduling and preemptive time-slicing.
With non-preemptive scheduling, the scheduler runs the current thread forever, requiring that thread explicitly to tell it when it is safe to start a different thread. With preemptive time-slicing, the scheduler runs the current thread until it has used up a certain tiny fraction of a second, and then "preempts" it, suspend()s it, and resume()s another thread for the next tiny fraction of a second.
Non-preemptive scheduling is very courtly, always asking for permission to schedule, and is quite valuable in extremely time-critical, real-time applications where being interrupted at the wrong moment, or for too long, could mean crashing an airplane.
Most modern schedulers use preemptive time-slicing, because, except for a few time-critical cases, it has turned out to make writing multithreaded programs much easier. For one thing, it does not force each thread to decide exactly when it should "yield" control to another thread. Instead, every thread can just run blindly on, knowing that the scheduler will be fair about giving all the other threads their chance to run.
It turns out that this approach is still not the ideal way to schedule threads. You've given a little too much control to the scheduler. The final touch many modern schedulers add is to allow you to assign each thread a priority. This creates a total ordering of all threads, making some threads more "important" than others. Being higher priority often means that a thread gets run more often (or gets more total running time), but it always means that it can interrupt other, lower-priority threads, even before their "time-slice" has expired.
The current Java release does not precisely specify the behavior of its scheduler. Threads can be assigned priorities, and when a choice is made between several threads that all want to run, the highest-priority thread wins. However, among threads that are all the same priority, the behavior is not well-defined. In fact, the different platforms on which Java currently runs have different behaviorssome behaving more like a preemptive scheduler, and some more like a non-preemptive scheduler.
To find out what kind of scheduler you have on your system, try the following:
public class RunnablePotato implements Runnable { public void run() { while (true) System.out.println(Thread.currentThread().getName()); } } public class PotatoThreadTester { public static void main(String argv[]) { RunnablePotato aRP = new RunnablePotato(); new Thread(aRP, "one potato").start(); new Thread(aRP, "two potato").start(); } }
For a non-preemptive scheduler, this prints the following:
one potato one potato one potato . . .
forever, until you interrupt the program. For a preemptive scheduler that time-slices, it repeats the line one potato a few times, followed by the same number of two potato lines, over and over:
one potato one potato ... one potato two potato two potato ... two potato . . .
until you interrupt the program. What if you want to be sure the two threads will take turns, no matter what the system scheduler wants to do? You rewrite RunnablePotato as follows:
public class RunnablePotato implements Runnable { public void run() { while (true) { System.out.println(Thread.currentThread().getName()); Thread.yield(); // let another thread run for a while } } }
The yield() method explicitly gives any other threads that want to run a chance to begin running. (If there are no threads waiting to run, the thread that made the yield() simply continues.) In our example, there's another thread that's just dying to run, so when you now
execute the class ThreadTester, it should output the following:
one potato two potato one potato two potato one potato two potato . . .
even if your system scheduler is non-preemptive, and would never normally run the second thread.
To see whether priorities are working on your system, try this:
public class PriorityThreadTester { public static void main(String argv[]) { RunnablePotato aRP = new RunnablePotato(); Thread t1 = new Thread(aRP, "one potato"); Thread t2 = new Thread(aRP, "two potato"); t2.setPriority(t1.getPriority() + 1); t1.start(); t2.start(); // at priority Thread.NORM_PRIORITY + 1 } }
If one potato is the first line of output, your system does not preempt using priorities.
Why? Imagine that the first thread (t1) has just begun to run. Even before it has a chance to print anything, along comes a higher-priority thread (t2) that wants to run right away. That higher-priority thread should preempt (interrupt) the first, and get a chance to print two potato before t1 finishes printing anything. In fact, if you use the RunnablePotato class that never yield()s, t2 stays in control forever, printing two potato lines, because it's a higher priority than t1 and it never yields control. If you use the latest RunnablePotato class (with yield()), the output is alternating lines of one potato and two potato as before, but starting with two potato.
Here's a good, illustrative example of how complex threads behave:
public class ComplexThread extends Thread { private int delay; ComplexThread(String name, float seconds) { super(name); delay = (int) seconds * 1000; // delays are in milliseconds start(); // start up ourself! } public void run() { while (true) { System.out.println(Thread.currentThread().getName()); try { Thread.sleep(delay); } catch (InterruptedException e) { return; } } } public static void main(String argv[]) { new ComplexThread("one potato", 1.1F); new ComplexThread("two potato", 1.3F); new ComplexThread("three potato", 0.5F); new ComplexThread("four", 0.7F); } }
This example combines the thread and its tester into a single class. Its constructor takes care of naming (itself) and of starting (itself), because it is now a Thread. The main() method creates new instances of its own class, because that class is a subclass of Thread. run() is also more complicated, because it now uses, for the first time, a method that can throw an unexpected exception.
The Thread.sleep() method forces the current thread to yield() and then waits for at least the specified amount of time to elapse before allowing the thread to run again. It might be interrupted, however, while sleeping by another thread. In such a case, it throws an InterruptedException. Now, because run() is not defined as throwing this exception, you must "hide" the fact by catching and handling it yourself. Because interruptions are usually requests to stop, you should exit the thread, which you can do by simply returning from the run() method.
This program should output a repeating but complex pattern of four different lines, where every once in a great while you see the following:
. . . one potato two potato three potato four . . .
You should study the pattern output to prove to yourself that true parallelism is going on inside Java programs. You may also begin to appreciate that, if even this simple set of four threads can produce such complex behavior, many more threads must be capable of producing near chaos if not carefully controlled. Luckily, Java provides the synchronization and thread-safe libraries you need to control that chaos.
Today, you learned that parallelism is desirable and powerful, but introduces many new problemsmethods and variables now need to be protected from thread conflictsthat can
lead to chaos if not carefully controlled.
By "thinking multithreaded," you can detect the places in your programs that require synchronized statements (or modifiers) to make them thread-safe. A series of Point examples demonstrated the various levels of safety you can achieve, while ThreadTesters showed how subclasses of Thread, or classes that implement the Runnable interface, are created and run() to generate multithreaded programs.
You also learned how to yield(), how to start(), stop(), suspend(), and resume() your threads, and how to catch ThreadDeath whenever it happens.
Finally, you learned about preemptive and non-preemptive scheduling, both with and without priorities, and how to test your Java system to see which of them your scheduler is using.
This wraps up the description of threads. You now know enough to write the most complex of programs: multithreaded ones. As you get more comfortable with threads, you may begin to use the ThreadGroup class or to use the enumeration methods of Thread to get your hands on all the threads in the system and manipulate them. Don't be afraid to experiment; you can't permanently break anything, and you only learn by trying.
Q: If they're so important to Java, why haven't threads appeared throughout the entire book?
A: Actually, they have. Every stand-alone program written so far has "created" at least one thread, the one in which it is running. (Of course the system created that Thread for it automatically.)
Q: How exactly do these threads get created and run? What about applets?
A: When a simple, stand-alone Java program starts up, the system creates a main thread, and its run() method calls your main() method to start your programyou do nothing to get that Thread. Likewise, when a simple applet loads into a Java-capable browser, a Thread has already been created by the browser, and its run() method calls your init() and start() methods to start your program. In either case, a new Thread() of some kind was done somewhere, by the Java environment itself.
Q: The ThreadTester class has an infinite loop that creates Threads and then join()s with them. Is it really infinite?
A: In theory, yes. In actuality, how far the loop runs determines the resource limits of (and tests the stability of) the threads package and garbage collector in your Java release. Over time, all Java releases will converge on making the loop truly infinite.
Q: I know Java releases are still a little fuzzy about the scheduler's behavior, but what's the current story?
A: Here are the gruesome details, relayed by Arthur van Hoff at Sun: the way Java schedules threads "...depends on the platform. It is usually preemptive, but not always time-sliced. Priorities are not always observed, depending on the underlying implementation." This final clause gives you a hint that all this confusion is an implementation problem, and that in some future release, the design and implementation will both be clear about scheduling behavior.
Q: Does Java support more complex multithreaded concepts, such as semaphores?
A: The class Object in Java provides methods that can be used to build up condition variables, semaphores, and any higher-level parallel construct you might need. The method wait() (and its two variants with a timeout) causes the current thread to wait until some condition has been satisfied. The method notify() (or notifyAll()), which must be called from within a synchronized method or block, tells the thread (or all threads) to wake up and check that condition again, because something has changed. By careful combinations of these two primitive methods, any data structure can be manipulated safely by a set of threads, and all the classical parallel primitives needed to implement published parallel algorithms can be built.
Q: My parallel friends tell me I should worry about something called "deadlock." Should I?
A: Not for simple multithreaded programs. However, in more complicated programs, one of the biggest worries does become one of avoiding a situation in which one thread has locked an object and is waiting for another thread to finish, while that other thread is waiting for the first thread to release that same object before it can finish. That's a deadlockboth threads will be stuck forever. Mutual dependencies like this involving more than two threads can be quite intricate, convoluted, and difficult to locate, much less rectify. They are one of the main challenges in writing complex multithreaded programs.