Hi, Habr! I present to you the translation of the article "The Java Language Specification ( Chapter 17. Threads and Locks )" Original.Chapter 17. Threads and Locks (Chapter 17. Threads and Locks)While most of the discussions in previous chapters concerned only the behavior of code executed simultaneously and as a single statement or expression at the same time, i.e. in one thread, a JVM (Java virtual machine) can simultaneously support multiple execution threads. These threads independently of each other use code that acts on values ​​and objects that are in shared memory. Threads can be supported by using multiple hardware processors, temporal separation of a single hardware processor, or temporal separation of multiple hardware processors.
')
Threads are represented by the
Thread class. The only way a user can create a thread is to create an object of this class; each thread is associated with some object. The thread will begin its execution when the
start () method is called on the corresponding
Thread object.
The behavior of threads, especially when synchronization is incorrect, may be incomprehensible and not in line with expectations. This chapter describes the semantics of multi-threaded programming; it contains the rules according to which values ​​can be seen for reading in shared memory, which is updated by many threads. Since the specification is similar to
Memory Models for different architectures, this semantics is known as the
Memory Model of the Java programming language. When there is no confusion, we will simply call these rules the "
Memory Model ".
This semanika does not prescribe how a multi-threaded program should be executed. Rather, it describes the possible behavior that multi-threaded programs can demonstrate. Any implementation strategy that generates possible behaviors is acceptable.
17.1 Synchronization (17.1. Synchronization)The Java programming language provides many mechanisms for inter-thread communication. The most fundamental of them are synchronization methods (synchronization), which is performed using monitors. Each object in Java is associated with a monitor, which a thread can lock or unlock (lock / unlock). At the same time, only one thread can hold a monitor. Any other threads that attempt to capture this monitor are blocked until they can capture it. Thread
t can block a specific monitor multiple times; when the monitor is released (unlock), the effect of a single lock operation is canceled.
The synchronized (
§14.19 )
statement computes a reference to an object, and then it tries to capture (lock) the monitor of this object and nothing happens until the capture is successful. After a successful lock, the body of the synchronized statement is executed. If the body of the synchronized statement is executed completely or in an abbreviated version, then this monitor is automatically released (unlock).
The synchronized method (
§8.4.3.6 ) automatically performs a lock (lock) on a call, its body is not executed until the lock (lock) is successfully executed. If we are dealing with an instance method, then it captures the monitor associated with the instance for which it was invoked (that is, an object that will be known as this during the execution of the method body). If the method is static, it captures the monitor associated with the Class object that represents the class in which the method is defined. If the execution of the method body is completed completely or in an abbreviated version, this monitor is automatically released.
The Java programming language does not prevent and does not require the definition of a deadlock condition. Programs where threads hold (directly or indirectly) seizure on multiple objects should use common tricks to avoid deadlocking. Create high-level locking primitives that do not have deadlocks, if necessary.
Other mechanisms, such as reading and writing volatile variables, and using classes from the java.util.concurrent package provide alternative ways to synchronize.
17.2 A set of expectations and notifications (17.2. Wait Sets and Notification)Each object, in addition to what is associated with the monitor, is also associated with a set of expectations (Wait Sets). A set of expectations is a set of threads.
When an object is first created, its wait set is empty. Elementary actions that add or remove threads to / from the set of expectations are atomic. The set of expectations is managed exclusively through the
Object.wait ,
Object.notify , and
Object.notifyAll methods .
The manipulation of the set of expectations can also be affected by the static interruption of the thread and the methods of the class
Thread associated with the interruption. In addition, the
Thread class methods for
sleeping and
joining other threads have properties obtained from the action properties of the
wait and
notification methods.
17.2.1. Waiting (17.2.1. Wait)The wait action occurs when the
wait () method is called or with the
wait (long millisecs) and
wait (long millisecs, int nanosecs) time signatures.
A call to
wait (long millisecs) with a parameter of zero or a call to
wait (long millisecs, int nanosecs) with the two parameters specified is equal to zero equivalent to a call to
wait () .
The thread returns and waits if it does not throw an
InterruptedException exception.
Suppose the thread
t performs the
wait method on the object
m , and let
n be the number of blocking actions on
t by
m that were not associated with unblocking actions. One of the following actions will occur:
- If n is zero (ie, the thread t has not yet captured the lock (lock) on the target m object), then an IllegalMonitorStateException will be thrown.
- If this wait with the given time signature nanosecs argument is not in the range of 0-999999 or the millisecs argument is specified with a negative number, then the IllegalArgumentException exception will be thrown
- If the thread t is interrupted, then the InterruptedException exception will be thrown and the state of interruption (interruption status) t is set to false.
- Otherwise, the following sequence occurs.
- The thread t is added to the wait set of object m , and performs n unlocks (unlock) on M.
- The thread t does not perform any more instructions until it is removed from the set of expectations of the object m . A thread can be removed from the wait set for any of the following reasons and will be restored sometime later:
- The notify action was performed on m , in which t is selected to be removed from the wait set.
- The notifyAll action is performed on m .
- The interrupt action is performed on t .
- If wait with a given time signature, an internal action removes t from the set of expectations m , which occurs after at least millisecs plus nanosecs after the start of this wait action.
- Internal action through implementation. The implementation is allowed, although not desirable, to perform “spurious wake-ups”, that is, remove the thread from the set of expectations and thus allow the renewal of actions without additional instructions for this.
Note that this provision requires coding practice in Java, using wait only inside loops, which end only by the logical condition that the thread holds the lock.
Each thread is required to determine the order of events that can cause it (ie, this thread) to remove from the set of expectations. This order should not be consistent with other arrangements, but the thread should behave as if these events occurred in that order.
For example, if the thread t is in the set of expectations for m , then the interrupt t and the notification occur. These events must occur in some order. If we assume that an interrupt occurred first, then t is eventually returned from wait with an exception throw InterruptedException and some other threads in the expectation set m (if they exist at the time of the notification) should receive a notification. If we assume that a notification first occurred, then t in the usual way will eventually return from wait and the interrupt will be in standby mode.
- Thread t execute n locks per m .
- If the thread t was removed from the set of expectations m in step 2 due to an interrupt, then the interrupt status t is set to false and the wait method asks for InterruptedException .
17.2.2. Notification (17.2.2. Notification)Notification (notification) occurs when calling the method
notify and
notifyAll .
Let's imagine that the thread
t will use any of these methods on the object
m , and let
n be the number of locks captured by
t by
m , which did not correspond to the number of execution of monitor release (unlock) actions.
One of the following actions will occur:
- If n is zero, an IllegalMonitorStateException will be thrown.
This is the case when the thread t no longer has a lock for the target m- object. - If n is greater than zero and this is a notify action, then if the wait set m is not empty, thread u is selected that is a member of the current wait set m and is removed from the wait set.
There is no guarantee which thread from the set of expectations will be selected. Deleting a thread u from a set of waits resumes u in a wait-action. Note, however, that the capture action u , when resuming, will be performed some time after t completely unlocks the monitor for m . - If n is greater than zero and a notifyAll action is performed, then all threads are removed from the set of expectations m and thus resume.
Notice, however, that at the same time, only one of them will capture the required monitor while resuming the wait.
17.2.3. Interrupts (17.2.3. Interruptions)Interruptions occur when
Thread.interrupt is called, as well as methods intended to call in turn, such as
ThreadGroup.interrupt .
Let
t be called
u.interrupt , for some thread
u , where
t and
u can be the same. These actions set the interrupt status
u to true.
Additionally, if there exists an object
m whose set of expectations contains
u , then
u is removed from the set of expectations
m . This includes
u for resuming in a wait-action; in this case, after re-capturing the monitor
m, an InterruptedException will be thrown.
Calls to
Thread.isInterrupted can determine thread interruption statuses. The static method
Thread.interrupted can be called in a thread to monitor and clear its own interrupt status.
17.2.4. Interaction of Expectations, Notifications, and Interruptions (17.2.4. Interactions of Waits, Notification, and Interruption)The above specifications allow us to define some properties related to the interaction of expectations, notifications and interrupts.
If the thread is notified and interrupted during the wait, it can either:
- Return normally to wait, while still in the interrupt standby mode (in other words, calling Thread.interrupted will return true)
- Will return from waiting with an exception throw InterruptedException
A thread may not reset this stats interrupt and return normally from a wait call.
Similarly, notifications can not be lost due to interrupts. Suppose that the set of
s threads in the set of expectations of the object
m , and the other thread performs a
notify on
m . Then either:
- At least one thread and s should return normally and wait, or
- all threads from s should exit and throw InterruptedException
Notice that if the thread is both interrupted and woken through notify, and that this thread is returned from waiting by throwing
InterruptedException , then any other thread in the expectation set should be notified.
17.3. Sleep and Leap (17.3. Sleep and Yield)Thread.sleep puts the working thread into sleep mode (temporary cessation of execution) for a certain period, depending on the accuracy of timers (system timers) and system schedulers (schedulers). A thread does not lose control over monitors and its action resumes depending on the scheduling and availability of processors on which threads can be executed.
It is important to mention that neither
Thread.sleep nor
Thread.yield have any synchronization semantics. In particular, the compiler should not write to the cache on registers outside of shared memory before calling
Thread.sleep or
Thread.yield , the compiler also should not overload the values ​​of cache registers after calling
Thread.sleep or
Thread.yield .
For example, the following (not correct) code segment, suppose that this.done is a non-volatile boolean field.
while (!this.done) Thread.sleep(1000);
The compiler reads this.done only once in the cache, and after that uses the values ​​from the cache for each iteration of the loop. This means that the loop will never end, even if another thread changes the value of this.done.
The following parts will be presented:
Part 2) Memory Model;
Part 3) Semantics of final fields; Word Tearing on some processors (x32); not atomic support for
double and
long .
Thanks for attention!:)