📜 ⬆️ ⬇️

The Java Language Specification. Chapter 17. Threads and Locks (Translation. Part 1)

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:


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:


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:


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:


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!:)

Source: https://habr.com/ru/post/358880/


All Articles