Threads And The Java Language
To create a thread using the Java language, you instantiate an object of type
Thread (or a subclass) and send it the
start() message. (A program can send the
start() message to any object that implements the
Runnable interface.) The definition of each thread's behavior is contained in its
run() method. A run method is equivalent to
main() in a traditional program: a thread will continue running until
run() returns, at which point the thread dies.
Most applications require threads to communicate and synchronize their behavior to one another. The simplest way to accomplish this task in a Java program is with locks. To prevent multiple accesses, threads can acquire and release a lock before using resources. Imagine a lock on the copy machine for which only one worker can possess a key at a time. Without the key, use of the machine is impossible. Locks around shared variables allow Java threads to quickly and easily communicate and synchronize. A thread that holds a lock on an object knows that no other thread will access that object. Even if the thread with the lock is preempted, another thread cannot acquire the lock until the original thread wakes up, finishes its work, and releases the lock. Threads that attempt to acquire a lock in use go to sleep until the thread holding the lock releases it. After the lock is freed, the sleeping thread moves to the ready-to-run queue.
In Java programming, each object has a lock; a thread can acquire the lock for an object by using the
keyword. Methods, or synchronized blocks of code, can only be executed
by one thread at a time for a given instantiation of a class, because
that code requires obtaining the object's lock before execution.
Continuing with our copier analogy, to avoid clashing copiers, we can
simply synchronize access to the copier resource, allowing only one
worker access at a time, as shown in the following code sample. We
achieve this by having methods (in the
Copier object) that modify the copier state be declared as synchronized methods. Workers that need to use a
Copier object have to wait in line because only one thread per
Copier object can be executing synchronized code.
Often, using a lock at the object level is too coarse. Why lock up an entire object, disallowing access to any other synchronized method for only brief access to shared resources? If an object has multiple resources, it's unnecessary to lock all threads out of the whole object in order to have one thread use only a subset of the thread's resources. Because every object has a lock, we can use dummy objects as simple locks, as shown here:
These methods need not be synchronized at the method level by declaring the whole method with the
synchronized keyword; they are using the member locks, not the object-wide lock that a synchronized method acquires.