Java multi-thread reentrantlock and condition

Source: Internet
Author: User
Tags finally block lock queue

Reference Link: 8288251

Reentrantlock Class 1.1 What is the lock framework in Reentrantlock Java.util.concurrent.lock is an abstraction of locking, which allows the implementation of a lock to be implemented as a Java class, rather than as a language feature. This leaves space for the various implementations of lock, which may have different scheduling algorithms, performance characteristics, or locking semantics. The Reentrantlock class implements lock, which has the same concurrency and memory semantics as synchronized, but adds features like lock polling, timed lock-in, and interruptible lock waiting. In addition, it provides better performance in the case of intense contention. (In other words, when many threads are trying to access a shared resource, the JVM can spend less time dispatching the thread and more of it to the execution thread.) What does the reentrant lock mean? Simply put, it has a lock-related get counter, if one of the threads that owns the lock gets the lock again, then the fetch counter increases by 1, and then the lock needs to be freed two times to get a true release. This mimics the semantics of synchronized; If a thread enters a synchronized block that is protected by a monitor that the thread already owns, it allows the thread to continue and does not release the lock when the thread exits the second (or subsequent) synchronized block. The lock is released only when the thread exits the first synchronized block that the monitor protects when it enters. Comparison of 1.2 Reentrantlock and synchronized

Same: Reentrantlock provides synchronized similar functionality and memory semantics.

Different:

(1) Reentrantlock functionality is more comprehensive, such as time lock waiting, can interrupt lock wait, lock vote, etc., so more extensibility. In the case of multiple condition variables and highly competitive locks, it is more appropriate to use Reentrantlock, Reentrantlock also provides condition, more flexibility for thread wait and wake operations, and a reentrantlock can have multiple condition instances , so it's more scalable.

(2) Reentrantlock performance is better than synchronized.

(3) Reentrantlock provides a polling lock request, he can try to get the lock, if successful, continue processing, get unsuccessful, can wait for the next run time processing, so it is not easy to create a deadlock, and synchronized once enter the lock request or success, It's always blocked, so it's easier to create deadlocks.

Features of the 1.3 reentrantlock extension

1.3.1 Implementing a polling lock request in an internal lock, a deadlock is fatal-the only way to recover is to restart the program, and the only way to prevent it is to build the program without errors. The lock acquisition mode that can be polled has a more perfect error recovery mechanism, which can avoid the occurrence of deadlock.
If you can't get all the locks you need, then using a polling method allows you to regain control, releasing the locks you've acquired, and then trying again. The lock acquisition mode that can be polled is implemented by the Trylock () method. This method acquires the lock only if the call to Shi is idle. If the lock is available, the lock is acquired and immediately returns the value TRUE. If the lock is not available, this method returns a value of false immediately. The typical usage statement for this method is as follows:
    1. Lock lock = ...;
    2. if (Lock.trylock ()) {
    3. try {
    4. //Manipulate protected state
    5. } finally {
    6. Lock.unlock ();
    7. }
    8. } Else {
    9. //Perform alternative actions
    10. }

1.3.2 Implement a timed lock request when an internal lock is used, the lock cannot be stopped once the request is started, so an internal lock poses a risk for implementing a time-bound activity. To solve this problem, you can use a timed lock. When a life with a time limit
The blocking method is called, and the timed lock can set the corresponding timeout in the time budget. If the activity does not get results within the expected time period, the timing lock can cause the program to return early. The timing lock acquisition mode is implemented by the Trylock (long, Timeunit) method.
1.3.3 implements an interruptible lock fetch request an INTERRUPTIBLE lock fetch operation allows for use in a removable activity. The lockinterruptibly () method allows you to receive a lock when the response is interrupted.

1.4 Reentrantlock bad and need to be noticed (1) lock must be released in the finally block. Otherwise, if the protected code throws an exception, the lock may never be released! This difference may seem like nothing, but in fact it is extremely important. Forgetting to release a lock in a finally block may leave a time bomb in the program, and when the bomb explodes one day, it takes a lot of effort to find the source. With synchronization, the JVM will ensure that locks are automatically freed (2) when the JVM manages lock requests and releases with synchronized, the JVM can include locking information when generating a thread dump. These are very valuable for debugging because they can identify the source of deadlocks or other unusual behavior. The lock class is just a normal class, and the JVM does not know which thread owns the lock object. Second, the condition variable condition

The condition variable is to a large extent to solve the problem that Object.wait/notify/notifyall is difficult to use.

A condition (also known as a conditional queue or condition variable ) provides a means for a thread to suspend the thread (that is, let it "wait") until another thread that a state condition might now be true notifies it. Because access to this shared state information occurs in different threads, it must be protected so that a form of lock is associated with that condition. The primary property for waiting to provide a condition is to atomically release the associated lock and suspend the current thread as if it were Object.wait done.

The API description above indicates that the condition variable needs to be bound to the lock, and that multiple condition need to be bound to the same lock. As mentioned in the previous lock, the method to get a condition variable is lock.newcondition ().

  1. void await() throws interruptedexception;
  2. void awaituninterruptibly();
  3. Long awaitnanos(long Nanostimeout) throws interruptedexception;
  4. Boolean await(long time, Timeunit unit) throws interruptedexception;
  5. boolean awaituntil(Date deadline) throws interruptedexception;
  6. void signal();
  7. void signalall();


The above is the method defined by the condition interface,await* corresponds to object.wait,signal corresponds to object.notify, Signalall corresponds to object.notifyall. Specifically, the condition interface changes the name to avoid confusion with the semantics and use of Wait/notify/notifyall in object, because condition also has wait/notify/ Notifyall method.

Each lock can have arbitrary data of the Condition object, Condition is bound with lock, so there is the fairness of lock feature: if it is a fair lock, the thread is released from condition.await in the order of FIFO , if the non-fair lock, then the subsequent lock competition will not guarantee the FIFO order.

An example of a model using condition to implement producer consumers is as follows.

  1. import java.util.concurrent.locks.Condition;
  2. import Java.util.concurrent.locks.Lock;
  3. import Java.util.concurrent.locks.ReentrantLock;
  4. public class productqueue<t> {
  5. private final t[] items;
  6. Private Final lock lock = new Reentrantlock ();
  7. private Condition notfull = Lock.newcondition ();
  8. private Condition Notempty = Lock.newcondition ();
  9. //
  10. Private int head, tail, count;
  11. Public productqueue(int maxSize) {
  12. Items = (t[]) new object[maxsize];
  13. }
  14. Public productqueue() {
  15. This (ten);
  16. }
  17. Public void put(T t) throws interruptedexception {
  18. Lock.lock ();
  19. try {
  20. While (count = = getcapacity ()) {
  21. Notfull.await ();
  22. }
  23. Items[tail] = t;
  24. if (++tail = = Getcapacity ()) {
  25. tail = 0;
  26. }
  27. ++count;
  28. Notempty.signalall ();
  29. } finally {
  30. Lock.unlock ();
  31. }
  32. }
  33. Public T take() throws interruptedexception {
  34. Lock.lock ();
  35. try {
  36. While (count = = 0) {
  37. Notempty.await ();
  38. }
  39. T ret = Items[head];
  40. Items[head] = null; GC
  41. //
  42. if (++head = = Getcapacity ()) {
  43. Head = 0;
  44. }
  45. --count;
  46. Notfull.signalall ();
  47. return ret;
  48. } finally {
  49. Lock.unlock ();
  50. }
  51. }
  52. Public int getcapacity() {
  53. return items.length;
  54. }
  55. Public int size() {
  56. Lock.lock ();
  57. try {
  58. return count;
  59. } finally {
  60. Lock.unlock ();
  61. }
  62. }
  63. }


In this example, consuming take () requires that the queue is not empty, if it is empty, hang (await ()) until the Notempty signal is received, and the production put () requires a queue If it is full, hang (await ()) until the notfull signal is received.

There may be a problem, if a thread lock () object is suspended and there is no unlock, then another thread will not be able to get the lock (thelock () operation will hang), then it cannot be notified (notify the previous thread, wouldn't that be a "deadlock"?

2.1 await* operation

As mentioned in the previous section, multiple Reentrantlock are exclusive locks, and if a thread is not released after the lock is taken, then another thread must not be locked, so the Lock.lock () and Lock.unlock ( ) may have a lock-release operation (as well as an action to acquire a lock). We look back at the code, regardless of whether take () or put (), the only operation that can release the lock after entering Lock.lock ( ) is await () . That is, the await () operation actually releases the lock, then suspends the thread, wakes up once the condition is satisfied, and acquires the lock again!

  1. Public final void await() throws interruptedexception {
  2. if (thread.interrupted ())
  3. throw new Interruptedexception ();
  4. Node node = Addconditionwaiter ();
  5. int savedstate = fullyrelease (node);
  6. int interruptmode = 0;
  7. While (!isonsyncqueue (node)) {
  8. Locksupport.park (this);
  9. if ((Interruptmode = checkinterruptwhilewaiting (node))! = 0)
  10. Break ;
  11. }
  12. if (acquirequeued (node, savedstate) && interruptmode! = Throw_ie)
  13. Interruptmode = Reinterrupt;
  14. if (node.nextwaiter! = null)
  15. Unlinkcancelledwaiters ();
  16. if (interruptmode! = 0)
  17. Reportinterruptafterwait (Interruptmode);
  18. }


Above is the code snippet for await () . As mentioned in the previous section, Aqs needs to have a CHL FIFO queue when acquiring a lock, so for a condition.await () , if you release the lock, you need to go to the queue and wait for the lock to be notified if you want to get the lock again. The complete await () operation is performed with the following steps:

      1. Joins the current thread to the Condition lock queue. In particular, this is different from the Aqs queue, where the Condition FIFO queue is entered. This structure is discussed in detail later. Proceed to 2.
      2. Release the lock. You can see that the lock is released, or else the thread cannot get the lock and a deadlock occurs. Proceed to 3.
      3. Spin (while) hangs until awakened or timed out or cacelled. Proceed to 4.
      4. Gets the lock (acquirequeued). and release yourself from the Condition FIFO queue, indicating that you no longer need the lock (I've got the lock).

Here we look back at the data structure of Condition . We know that a Condition can be await* in multiple places (), then we need a FIFO structure to concatenate these Condition . Then wake up one or more (usually all) as needed. Therefore, a FIFO queue is required inside the Condition .

  1. private transient Node firstwaiter;
  2. private transient Node lastwaiter;

The two nodes above are the queues that describe a FIFO. We then combine the node data structure mentioned earlier. We found out that Node.nextwaiter was in handy! Nextwaiter is a series of condition.await* in series together to form a FIFO queue.

2.2 Signal/signalall operation

await* () is clear, it is much easier to see signal/signalall now. According to Signal/signalall , the first node in the FIFO queue in condition.await* () is awakened (or all node). Although all node may be awakened, it is still only one thread that can get the lock, and the other threads that do not get the lock still need to spin the wait, which is the 4th step mentioned above (acquirequeued).

  1. private void dosignal(Node first) {
  2. Do {
  3. if ((Firstwaiter = first.nextwaiter) = = null)
  4. Lastwaiter = null;
  5. First.nextwaiter = null;
  6. } while (!transferforsignal (first) &&
  7. (first = firstwaiter)! = null);
  8. }
  9. private void dosignalall(Node first) {
  10. Lastwaiter = Firstwaiter = null;
  11. Do {
  12. Node next = First.nextwaiter;
  13. First.nextwaiter = null;
  14. Transferforsignal (first);
  15. First = next;
  16. } while (first! = null);
  17. }


The above code is easy to see,signal is to wake up the first non-cancelled node thread in the condition queue, and Signalall is to wake up all non-cancelled node threads. Of course, encountering a cancelled thread requires that it be removed from the FIFO queue.

    1. Final boolean transferforsignal(node node) {
    2. if (!compareandsetwaitstatus (node, node.condition, 0))
    3. return false;
    4. Node P = Enq (node);
    5. int c = p.waitstatus;
    6. if (C > 0 | |!compareandsetwaitstatus (P, C, node.signal))
    7. Locksupport.unpark (Node.thread);
    8. return true;
    9. }


This is the process of waking up a await* () thread, as described in the previous section, if you want to Unpark the thread and get the thread to the lock, you need the thread node to enter the Aqs queue. So you can see that the Enq (node) operation was called before Locksupport.unpark , adding the current node to the Aqs queue.

Java multi-thread reentrantlock and condition

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.