Back to Home

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

java · multithreading · memory model

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

Hello, 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

While most of the discussions in the previous chapters concerned only the behavior of the code executed simultaneously and as a single statement or expression at the same time, i.e. in one thread, the JVM (Java virtual machine) can support multiple threads at the same time. These threads independently use code that acts on values ​​and objects located in shared main memory. Threads can be supported by using multiple hardware processors, temporarily splitting a single hardware processor, or temporarily splitting 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 kind of object. The thread will start its execution when the start () method is called on the corresponding Thread object.
The behavior of threads, especially when synchronization is not performed correctly, may be incomprehensible and not meet expectations. This chapter describes the semantics of multithreaded programming; it contains 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 various architectures, this semantics is known as Memory ModelJava programming language. When there is no confusion, we will simply call these rules " Memory Model ".

This semantic does not prescribe how a multithreaded program should be executed. Rather, it describes the possible behavior that multi-threaded programs may exhibit. Any execution strategy that generates possible patterns of behavior is acceptable.

17.1 Synchronization (17.1. Synchronization)

The Java programming language provides many mechanisms for interacting between threads. The most fundamental of these are synchronization methods, which are implemented using monitors. Each object in Java is associated with a monitor, which the thread can capture or release (lock / unlock). Only one thread can hold a monitor at a time. Any other threads trying to capture this monitor are blocked until they can capture it. Thread t can block a specific monitor many times; when the monitor is released (unlock), the effect of one lock operation is canceled. Synchronized

statement ( §14.19) computes a reference to the object, and then it tries to lock the monitor of this object and nothing else happens until the capture is successful. After a successful lock, the body of the synchronized statement is executed. If the body of the synchronized operator is executed in full or in abbreviated form, then this monitor is automatically released (unlock).

Synchronized Method ( §8.4.3.6) automatically performs a lock (lock) upon a call, its body is not executed until a lock is successfully completed. If we are dealing with an instance method, then it captures the monitor associated with the instance for which it was called (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, which represents the class in which the method is defined. If the execution of the method body is completed in full or in abridged form, this monitor is automatically released.
The Java programming language does not prevent and does not require the definition of deadlock conditions. Programs where threads hold (directly or indirectly) capture on multiple objects should use common tricks to avoid deadlocks. Create high-level locking primitives that don't have deadlocks, if necessary.

Other mechanisms, such as reading and writing volatile variables, and using classes from the java.util.concurrent package provide alternative synchronization methods.

17.2 Set of expectations and notifications (17.2. Wait Sets and Notification)

Each object, in addition to being associated with a 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 set of expectations is empty. Elementary actions that add or remove threads to / from a set of expectations are atomic. The set of expectations is controlled exclusively through the methods Object.wait , Object.notify , and Object.notifyAll .

The manipulation of the set of expectations can also be affected by the static interruption of the thread and thread class methods associated with interruption. In addition, the methods of the Thread class for sleeping and joining other threads have properties obtained from the properties of the actions 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 temporary signatures wait (long millisecs) and wait (long millisecs, int nanosecs) .

A wait (long millisecs) call with a parameter of zero or a wait (long millisecs, int nanosecs) call with two parameters specified equal to zero are equivalent to a wait () call .

The thread returns and waits if it does not throw an InterruptedException .
Suppose thread t executes the wait method on object m , and let n be the number of blocked actions on ton m , which were compared with the Unlock action. One of the following actions will occur:

  • If n is zero (that is, the t thread has not yet taken a lock on the target m object), then an IllegalMonitorStateException will be thrown .
  • If this wait with a given time signature is a nanosecs argument not in the range 0-999999 or a millisecs argument is specified with a negative number, then an IllegalArgumentException will be thrown
  • If the thread t is interrupted, then an InterruptedException will be thrown and the interruption status t will be set to false.
  • Otherwise, the following sequence holds.
    1. Thread t is added to the waiting set of object m , and performs n unlocks on M.
    2. Trades t does not perform any more instructions, until it is removed from the set of object expectations m . A thread can be deleted from the waiting 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 set of expectations.
      • NotifyAll action performed on m .
      • The interrupt action is executed on t .
      • If wait with a given time signature, the internal action removes t from the set of expectations m that occurs after at least millisecs plus nanosecs after the start of this wait action.
      • Internal action through implementation. Implementation is permitted, although not advisable, to perform “spurious wake-ups”, that is, remove the thread from the set of expectations and thus allow the resumption of actions without additional instructions for this.
        Please note that this situation requires the practice of coding in Java, when using wait only inside loops that end only under the logical condition that the thread holds the lock.

      Each thread must determine the order of events that can cause it (i.e. this thread) to be removed from the set of expectations. This order should not be consistent with other orderings, but the thread should behave as if these events occurred in this order.

      For example, if thread t is in the set of expectations for m , and then t is interrupted and notified. These events should occur in some order. If we assume that an interruption first occurred, then t eventually returns from wait with an InterruptedException and some other threads in the expectation set m(if they exist at the time of notification) must be notified. If we assume that a notification first occurred, then t in the usual manner will eventually return from wait, and the interrupt will be in standby mode.
    3. Thread t execute n locks on m .
    4. If the thread t was deleted from the set of expectations m in step 2 due to the interrupt, then the interrupt status t is set to false and the wait method asks for an InterruptedException .

17.2.2. Notification (17.2.2. Notification)
Notification occurs when the notify and notifyAll method is called .
Let's imagine that thread t will use any of these methods on object m , and let n be the number of lock locks by t by m , which did not correspond to the number of monitor release actions (unlock).
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 set of expectations m is not empty, the thread u is selected which is a member of the current set of expectations m and it is removed from the set of expectations.
    There is no guarantee which thread from the set of expectations will be selected. Removing the thread u from the set of expectations resumes u in the wait action. Note, however, that the capture action u , when resumed, will take place some time after t completely unlocks the monitor for m .
  • If n is greater than zero and the notifyAll action is performed , then all threads are removed from the expectation set m and thus resume.
    Note, however, that, at the same time, only one of them will capture the desired monitor while resuming wait.

17.2.3. Interrupts (17.2.3. Interruptions)
Interrupts (Interruptions) occur when calling Thread.interrupt , and methods adapted in turn for the call, such as ThreadGroup.interrupt .
Let t call 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 is some object m whose set of expectations contains u , then u is removed from the set of expectations m . It includes uto resume the wait action, in this case, after re-capturing monitor m, an InterruptedException will be thrown .
Calls to Thread.isInterrupted can determine thread abort statuses. The static Thread.interrupted method can be called on a thread to observe 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 interruptions.

If the thread is notified and interrupted during the wait, it can either:

  • Return normally to standby while still in interrupt standby mode (in other words, calling Thread.interrupted will return true)
  • Will return from waiting with an InterruptedException

The thread may not reset this interrupt state and return normally from the wait call.
Similarly, notifications cannot be lost due to interruptions. Suppose that a set of s threads in a set of expectations of an object m , and another thread does 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

Note that if the thread is both aborted and woken up via notify and that the thread returned from waiting by throwing an InterruptedException , then some other thread in the waiting set should be notified.

17.3. Sleep and Yield (17.3. Sleep and Yield)

Thread.sleep sets the working thread to sleep mode (temporary termination of execution) for a certain period of time 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 planning and availability of processors on which threads can be executed.
It is important to mention that neither Thread.sleep nor Thread.yieldhave no synchronization semantics. In particular, the compiler should not write cache to registers outside of shared memory before calling Thread.sleep or Thread.yield , and the compiler should not overload cache registers after calling Thread.sleep or Thread.yield .
For example, the next (incorrect) segment of code, suppose 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 cache values ​​every iteration of the loop. This means that the cycle will never be terminated, even if another thread changed the value of this.done.

In the following parts will be presented:

Part 2) Memory Model;
Part 3) Semantics of final fields; Word Tearing on some processors (x32); non atomic support for double and long .

Thanks for attention!:)

Read Next