Locks in Java
Locks, like synchronized synchronous blocks, are a thread synchronization mechanism, but more complex than the synchronized synchronization blocks in Java. Because locks (and other more advanced thread synchronization mechanisms) are implemented by synchronized synchronous blocks, we are not yet completely free of the Synchronized keyword (the translator notes: This is the case before Java 5 ).
Starting with Java 5, the Java.util.concurrent.locks package contains the implementation of some locks, so you don't have to implement your own locks. But you still need to know how to use these locks, and it's useful to understand the theory behind them. You can refer to my introduction to Java.util.concurrent.locks.Lock for more information about locks.
A simple lock.
Let's start with a synchronization block in Java:
public class counter{ private int count = 0; public Int Inc. () { synchronized (this) { return ++count;}} }
You can see that there is a synchronized (this) code block in the Inc () method. The code block guarantees that only one thread can execute return ++count at the same time. While the code in the synchronized synchronization block can be more complex, the simple operation of ++count is enough to express the meaning of thread synchronization.
The following counter class uses lock instead of synchronized to achieve the same purpose:
public class counter{ private lock lock = new Lock (); private int count = 0; public Int Inc () { lock.lock (); int newcount = ++count; Lock.unlock (); return newcount;} }
The lock () method locks the lock instance object so that all threads that call the lock () method on the object are blocked until the unlock () method of the lock object is called.
Here is a simple implementation of the lock class:
public class Counter{public class lock{ private Boolean isLocked = false; Public synchronized void Lock () throws interruptedexception{ while (isLocked) { wait (); } IsLocked = true; } public synchronized void Unlock () { isLocked = false; Notify (); }}
Notice the while (isLocked) loop, which is also called "Spin lock". The spin lock and the Wait () and notify () methods are described in more detail in this article. When IsLocked is true, the thread calling lock () blocks the wait on the wait () call. To prevent the thread from receiving a notify () call and returning from Wait (also known as spurious wakeup), the thread will recheck the islocked condition to determine whether it is currently safe to continue execution or if it needs to wait again, instead of thinking that the thread has been awakened and can proceed safely. If isLocked is false, the current thread exits the while (isLocked) loop and sets isLocked back to true so that other threads that are calling the lock () method can lock on the lock instance.
When the thread completes the code in the critical section (between lock () and unlock (), it calls unlock (). Execution unlock () re-sets IsLocked to false and notifies (wakes) one of the threads (if any) that called the Wait () function in the lock () method.
Re-entry of the lock
The synchronized synchronization block in Java is reentrant. This means that if a Java thread enters the synchronized synchronization block in the code, and therefore obtains a lock on the thread that corresponds to the synchronization object used by the synchronization block, then it can enter another Java code block that is synchronized by the same pipe object. Here is an example:
public class reentrant{public synchronized outer () { inner (); } Public synchronized inner () { //do something }}
Note that both outer () and inner () are declared as synchronized, which is equivalent in the Java and synchronized (this) block. If a thread calls outer (), it is not a problem to call inner () in outer () because both methods (code blocks) are synchronized by the same pipe object ("this"). If a thread already has a lock on a pipe object, it has access to all the blocks of code that are synchronized by the pipe object. This is reentrant. A thread can enter a block of code synchronized with any lock it already owns.
The lock implementations given earlier are not reentrant. If we rewrite the Reentrant class as follows, when the thread calls outer (), it will block at the Lock.lock () of the inner () method.
public class reentrant2{ lock lock = new Lock (); public outer () { lock.lock (); Inner (); Lock.unlock (); } Public synchronized inner () { lock.lock (); Do something lock.unlock (); }}
The thread that calls outer () locks the lock instance first, and then continues to call inner (). In the inner () method, the thread will try to lock the lock instance again, and the result will fail (that is, the thread will be blocked) because the lock instance is already locked in the outer () method.
Two times lock () does not call unlock (), the second call to lock will block, after seeing the lock () implementation, you will find the reason is obvious:
public class lock{ Boolean isLocked = false; Public synchronized void Lock () throws interruptedexception{ while (isLocked) { wait (); } IsLocked = true; } ...}
Whether a thread is allowed to exit the lock () method is determined by the condition in the while loop (Spin lock). The current judging condition is that the lock operation is allowed only if islocked is false, regardless of which thread has locked it.
In order for this lock class to be reentrant, we need to make a small change to it:
public class lock{ Boolean isLocked = false; Thread lockedby = null; int lockedcount = 0; Public synchronized void Lock () throws interruptedexception{ Thread callingthread = Thread.CurrentThread (); while (isLocked && lockedby! = callingthread) { wait (); } IsLocked = true; lockedcount++; Lockedby = Callingthread; } public synchronized void Unlock () { if (thread.curentthread () = = This.lockedby) { lockedcount--; if (Lockedcount = = 0) { isLocked = false; Notify (); }}} ...}
Notice that the current while loop (Spin lock) also takes into account the thread that has locked the lock instance. If the current lock object is not locking (isLocked = false), or if the current calling thread has locked the lock instance, then the while loop is not executed and the thread calling lock () can exit the method ( translator Note: "Allowed to exit the method" In the current semantics, it means not calling wait () and causing blocking.
In addition, we need to record the number of times the same thread repeatedly locks a lock object. Otherwise, a unblock () call will unlock the entire lock, even if the current lock has been locked more than once. We do not want the lock to be lifted until the number of times the unlock () call has reached the corresponding lock () call.
Now this lock class is reentrant.
The fairness of the lock
Java's synchronized blocks do not guarantee the order in which they are attempted into their threads. Therefore, if multiple threads are constantly competing to access the same synchronized synchronization block, there is a risk that one or more of the threads will never get access-that is, the access is always assigned to other threads. This situation is called thread starvation. In order to avoid this problem, the lock needs to achieve fairness. The locks shown in this article are internally implemented with synchronized synchronization blocks, so they are not guaranteed to be fair. There is more discussion of this content in hunger and equity.
Call Unlock () in the Finally statement
It is important to call unlock () in the finally statement if lock is used to protect the critical section and the critical section is likely to throw an exception. This ensures that the lock object can be unlocked so that other threads can continue to lock it. The following is an example:
Lock.lock (); try{ //do critical section Code, //which may throw exception} finally { lock.unlock ();}
This simple structure guarantees that the lock object can be unlocked when the critical section throws an exception. If the unlock () is not called in the finally statement, when the critical section throws an exception, the lock object stays in the locked state forever, which causes all other threads that call lock () on the lock object to block.
1. Communication through shared objects
A simple way to send signals between threads is to set the signal value in the variables of the shared object. Thread A sets the Boolean member variable hasdatatoprocess to true in a synchronous block, and thread B reads the hasdatatoprocess member variable in the synchronization block. This simple example uses a signal-holding object and provides a set and check method:
public class mysignal{ protected Boolean hasdatatoprocess = false; Public synchronized Boolean hasdatatoprocess () { return this.hasdatatoprocess; } Public synchronized void Sethasdatatoprocess (Boolean hasData) { this.hasdatatoprocess = HasData; }}
Threads A and B must obtain a reference to a Mysignal shared instance for communication. If they hold references that point to different mysingal instances, they will not be able to detect each other's signals. The data that needs to be processed can be stored in a shared cache, which is stored separately from the Mysignal instances.
2, Busy Waiting (Busy wait)
Thread B preparing to process the data is waiting for the data to become available. In other words, it waits for a signal from thread A, and this signal causes hasdatatoprocess () to return true. Thread B runs in a loop to wait for this signal:
protected Mysignal sharedsignal = ... while (!sharedsignal.hasdatatoprocess ()) { //do nothing ... busy waiting}
3. Wait (), notify () and Notifyall ()
Busy waiting does not make efficient use of the CPU running the waiting thread, unless the average wait time is very short. Otherwise, it is wiser to let the waiting thread go to sleep or non-running until it receives the signal it waits for.
Java has a built-in wait mechanism to allow threads to become non-operational when waiting for a signal. The Java.lang.Object class defines three methods, wait (), notify (), and Notifyall () to implement this wait mechanism.
Once a thread invokes the wait () method of any object, it becomes non-operational until another thread invokes the Notify () method of the same object. In order to call Wait () or notify (), the thread must first obtain the lock on that object. In other words, the thread must call Wait () or notify () in the synchronization block. The following is a modified version of Mysingal-mywaitnotify with Wait () and notify ():
public class Monitorobject{}public class mywaitnotify{ monitorobject mymonitorobject = new Monitorobject (); public void dowait () { synchronized (mymonitorobject) { try{ mymonitorobject.wait (); } catch ( Interruptedexception e) {...} }} public void Donotify () { synchronized (mymonitorobject) { mymonitorobject.notify ();}} }
The wait thread will call Dowait (), and the wake-up thread will call Donotify (). When a thread calls an object's Notify () method, a thread that is waiting for the object will be awakened and allowed to execute (glossing: The thread that will be awakened is random and cannot specify which thread to wake). It also provides a notifyall () method to wake all threads that are waiting for a given object.
As you can see, both the wait thread and the wake-up thread call Wait () and notify () in the synchronization block. This is mandatory! A thread cannot call wait (), notify (), or notifyall () if it does not hold an object lock. Otherwise, a Illegalmonitorstateexception exception is thrown.
(glossing: The JVM is so implemented that when you call wait it first checks if the current thread is the owner of the lock, not the illegalmonitorstateexcept, referring to the JVM source line 1422. )
But how is this possible? Does the wait thread hold the lock on the monitor object (Mymonitor object) While it executes inside the synchronization block? Does the wait thread not block the synchronization block that wakes the thread into donotify ()? The answer is: really not. Once the thread invokes the Wait () method, it releases the lock on the monitor object it holds. This will allow other threads to also call Wait () or notify ().
Once a thread is awakened, it cannot immediately exit the wait () method call until the thread that calls notify () exits its own synchronization block. In other words: The awakened thread must regain the lock on the monitor object before exiting the method call of Wait () because the wait method call runs inside the synchronization block. If multiple threads are awakened by Notifyall (), only one thread can exit the wait () method at the same time, because each thread must obtain a lock on the monitor object before exiting Wait ().
4. Lost signal (Missed signals)
The Notify () and Notifyall () methods do not save the method that calls them, because there is a possibility that no thread is waiting when these two methods are called. After the notification signal is discarded. Thus, if a thread calls notify () before the notified thread calls Wait (), the waiting thread will miss the signal. This may or may not be a problem. However, in some cases, this may cause the waiting thread to wait forever and no longer wake up because the thread missed the wake-up signal.
In order to avoid losing signals, they must be kept in the signal class. In the mywaitnotify example, the notification signal should be stored in a member variable of the Mywaitnotify instance. The following is a modified version of Mywaitnotify:
public class mywaitnotify2{ monitorobject mymonitorobject = new Monitorobject (); Boolean wassignalled = false; public void dowait () { synchronized (mymonitorobject) { if (!wassignalled) { try{ Mymonitorobject.wait (); } catch (Interruptedexception e) {...} } Clear signal and continue running. wassignalled = false; } } public void Donotify () { synchronized (mymonitorobject) { wassignalled = true; Mymonitorobject.notify ();}}}
Note that the Donotify () method sets the wassignalled variable to True before calling notify (). Also, be aware that the dowait () method checks the wassignalled variable before calling wait (). In fact, if no signal is received in the time period between the previous dowait () call and the dowait () call, it will only call wait ().
(glossing: To avoid signal loss, use a variable to save whether it has been notified.) Before notify, set yourself to have been notified. After wait, the settings themselves have not been notified and need to wait for notification. )
5. False wake-up
For inexplicable reasons, threads may wake up without calling Notify () and Notifyall (). This is called false wakeup (spurious wakeups). I woke up with no end.
If a false wake occurs in the Dowait () method of MyWaitNotify2, the waiting thread can perform subsequent operations even if it does not receive the correct signal. This can cause serious problems with your application.
To prevent false wakeup, the member variable that holds the signal is examined in a while loop, not in the IF expression. Such a while loop is called a spin lock (glossing: This is prudent, the current JVM implementation spin consumes the CPU, if the Donotify method is not called for a long time, the Dowait method will always spin and the CPU will consume too much). The awakened thread spins until the condition in the spin lock (while loop) becomes false. The revised version of the following MyWaitNotify2 shows this:
public class mywaitnotify3{ monitorobject mymonitorobject = new Monitorobject (); Boolean wassignalled = false; public void dowait () { synchronized (mymonitorobject) {while (!wassignalled) { try{ Mymonitorobject.wait (); } catch (Interruptedexception e) {...} } Clear signal and continue running. wassignalled = false; } } public void Donotify () { synchronized (mymonitorobject) { wassignalled = true; Mymonitorobject.notify ();}}}
Note that the wait () method is in a while loop, not in an if expression. If the waiting thread wakes without receiving a signal, the wassignalled variable changes to a false,while loop that executes again, prompting the waking thread to return to the waiting state.
6. Multiple threads waiting for the same signal
If you have multiple threads waiting to be awakened by Notifyall (), but only one is allowed to continue, using the while loop is a good way to do so. Only one thread can get a monitor object lock at a time, meaning that only one thread can exit the wait () call and clear the WASSIGNALLED flag (set to false). Once this thread exits the synchronization block of Dowait (), the other thread exits the wait () call and checks the value of the wassignalled variable in the while loop. However, this flag has been Cheng in addition to the first wake-up line, so the rest of the waking threads will return to the waiting state until the next signal arrives.
7. Do not call wait () in a string constant or global object
(glossing: A string constant in this chapter refers to a variable with a constant value)
A previous version of this article uses a string constant ("") as a pipe object in the mywaitnotify example. The following is the example:
public class mywaitnotify{ String mymonitorobject = ""; Boolean wassignalled = false; public void dowait () { synchronized (mymonitorobject) {while (!wassignalled) { try{ Mymonitorobject.wait (); } catch (Interruptedexception e) {...} } Clear signal and continue running. wassignalled = false; } } public void Donotify () { synchronized (mymonitorobject) { wassignalled = true; Mymonitorobject.notify ();}}}
The problem with calling Wait () and notify () in an empty string as a synchronization block (or other constant string) for a lock is that the jvm/compiler internally converts the constant string to the same object. This means that even if you have 2 different mywaitnotify instances, they all reference the same empty string instance. It also implies the risk that a thread calling dowait () on the first mywaitnotify instance will be awakened by a thread calling Donotify () on the second mywaitnotify instance. This picture can be drawn as follows:
At first it might not be a big problem. After all, if Donotify () is called on the second mywaitnotify instance, what really happens is that threads A and B are woken up incorrectly. The awakened thread (A or B) will check the signal value in the while loop and return to the wait state because donotify () is not called on the first Mywaitnotify instance, which is exactly the instance it is waiting for. This is the equivalent of triggering a false wake-up. Thread A or B wakes up when the signal value is not updated. However, the code handles this situation, so the thread returns to the waiting state. Remember that even if 4 threads call Wait () and notify () on the same shared string instance, the signals in dowait () and donotify () are saved by 2 mywaitnotify instances respectively. A donotify () call on the MYWAITNOTIFY1 may wake the MyWaitNotify2 thread, but the signal value will only be stored in the MyWaitNotify1.
The problem is that because donotify () only calls notify () instead of notifyall (), even if there are 4 threads waiting on the same string (empty string) instance, only one thread is awakened. So, if thread A or B is awakened by a signal sent to C or D, it checks its signal value to see if there is a signal being received and then returns to the waiting state. Both C and D are not awakened to check the signal value they actually receive, so the signal is lost. This situation is equivalent to the previously mentioned loss of the signal problem. C and D have been sent signals, but none of them can respond to the signal.
If the Donotify () method calls Notifyall () instead of notify (), all waiting threads are awakened and the signal values are checked sequentially. Threads A and B return to the wait state, but C or D only one thread notices the signal and exits the dowait () method call. Another in C or D returns to the wait state because the thread that obtained the signal clears the signal value (set to false) while exiting Dowait ().
After seeing the above, you may try to use Notifyall () instead of notify (), but this is a bad idea in terms of performance. There is no reason to wake all threads every time that only one thread can respond to a signal.
So: In the Wait ()/notify () mechanism, do not use global objects, string constants, and so on. The corresponding unique object should be used . For example, each instance of a MyWaitNotify3 (example in the previous section) has a monitor object that belongs to itself, instead of calling Wait ()/notify () on an empty string.
Glossing
Enhancement (English: Monitors, also known as a monitor) is an object or module that implements mutually exclusive access to shared resources for multiple worker threads. These shared resources are typically hardware devices or a bunch of variables. The tube is implemented at a point in time, with a maximum of one thread executing one of its subroutines. Compared with the concurrent programming, which implements mutually exclusive access by modifying the data structure, the process greatly simplifies the program design.
Take an example of the skewers under the knowledge point:
At school we often go to the library to borrow books, which I was very impressed (I would like to give an example of train tickets). Library, there is a book called "Java Concurrent programming Combat", small a morning when the book borrowed, small b at noon to the library to find this book, where small A and small b are two threads, they are to read the book is shared resources.
1. Communicating through shared resources
Little b went to the library and found that the book had been borrowed (hasdatatoprocess in the example). He returned home, waited a few days, then went to the library to find the book, found that the book has been returned, he successfully borrowed the book.
2. Busy waiting
In fact, small b in small a after a while will return the book back, small A but in a few days after the book to find. In order to borrow books early (reduce the delay), small a may be in the library waiting, every few minutes (while loop) he went to check whether the book has been returned, so long as small b a Get books back, small a will soon know.
3.wait (), notify () and Notifyall ()
Many times later, little a found himself doing so tired, the body a little too much. But soon, the school library system improved, joined the SMS notification function (notify ()), as long as small b A also get books back, immediately will SMS notice small a, so small a can sleep at home and other text messages.
4. Loss of Signal
The school library system is designed like this: When a book is returned, it sends a message to the waiting person, and only sends it once, and if there is no waiting, he will send it (but no receiver). The problem arises, because the text message will only be sent once, when the book is returned, no one waiting for the books, he will send an empty text message, but then there are waiting to borrow this book students will never receive text messages, resulting in the endless waiting for these classmates. To avoid this problem, we are waiting for a call to ask the librarian whether to continue waiting (if (!wassignalled)).
5. False Wakeup
The library system also has a bug: The system will occasionally give you clockwork error messages, the story can be borrowed (in fact, books can not borrow). We've already called the librarian, and he said let's wait for the text. We are very obedient, a short message (in fact, the bug caused by the error message), went to borrow books, to the library and found that the book did not return! We are depressed, but there is no way ah, the school does not fix the bug, we have to be smart: every time after receiving a text message, then call to ask whether the book can be borrowed (while (!wassignalled)).
The locking mechanism in Java