Reentrant mechanisms may be appreciated that lock

1, listen to the story of the knowledge mastered

In a village there, there is a well of water, water quality is very good, the villagers want to dig a well in the water. This is only a well, the village of so many people, so come to a draw with the rules of the job. Mayor brains, and ultimately came up with a reasonable plan, let's take a closer look at the wisdom and understanding of adult village.

The well to arrange a person to see well, fetch water to maintain order.

When fetching water, the family unit, the family to which anyone first to the well, you can hit the water, but if a family accounted kick right to come kick his family at this time do not line up. Those who did not seize the right to draw water, one by one, lined up next to the well, the top surface of the first arrival. Kick diagram below:

Schematic kick

Is not feeling very harmonious, if the kick had finished, he would look well with people reporting, people will see the well to fetch water and then a second person. So we are always able to hit the water. It is not seem quite fair, first man hit the water, of course, not absolutely fair, and see for yourself the following scenario:

Kick with his family

Watching a baby's father was fetching water, he also went to the well of the baby, so the women with the father directly into your front kick, the envy of others. 
The story above model is known as a fair lock model, when a person think of the well to fetch water, and now people who are not their own side kick, this time you have to obediently line up in the back of the queue.

Things are not always so easy, there is always some people want to take shortcuts, people saying well look older, sometimes, eyesight is not very good, this time, people began playing a new idea. New to kick people when they see people queuing to fetch water, they would not be so well-behaved pushed to the bottom to line up, on the contrary, they will now have to see that no one is to draw water, if someone fetch water , crash others, had routed queue in the back, but if this time fetching water in front of people just kick down the water, is the transfer, the team ranked in the human head is not yet complete handover, this time, new people can try to grab the right to draw water, if to grab, Oh, other people can only open one eye closed, because we are the default rule. This is called unfair lock model. The new man is not necessarily gotta obediently line up, which also resulted in the original queue, the queue of people may have to wait a long, long time. 
 

reentrant lock java implementation details -ReentrantLock

ReentrantLock supports two ways to acquire the lock, one is a fair model of a non-equity model. Before continuing, we first convert story elements into program elements.

Element conversion 
 

First is that we lock fair model:

Initialization, state = 0, indicates a kick no preemption right. At this time, the villagers fetch water A to (A thread lock request), account for the right kick, the state + 1, as follows:

A thread acquires the lock

A thread made locks, the state atomic +1, this time the state is changed to 1, A thread continues to perform other tasks, then the villagers would also like to draw water B (B request thread lock), thread B can not get a lock, generating node are queued, as shown below:

Thread B waits

Initialization time, will generate an empty head node, then the node is the thread B, at this time, if another thread A lock request, whether to wait in line? The answer of course is no, or else directly deadlock. A lock when requested again, is a period equivalent to kick, but also to draw water in the same family, is privileged, the state at this time as shown below:

Reentrant lock acquisition

 

Here one might ask how to understand this form of reentrant lock code inside it?

Copy the code

    public static ReentrantLock lock = new ReentrantLock();
    public static int i = 0;
    public void run() 
  {
        for (int j = 0;j<100000;j++) 
     {
            lock.lock();
            lock.lock();
            try {
                i++;
            }finally {
                lock.unlock();
                lock.unlock();
            }
        }
    }

Copy the code

 

Why do I need to use reentrant lock will be specifically described in the story described after;

 

Here, and I believe we should understand what is reentrant lock it. It is a thread after acquiring the lock again to acquire the same lock, this time just to state the value of accumulated. If the thread A releases a lock, it is to be so:

A thread releases a lock

Just cut the state value, only the thread A full release of this lock, the state value reduced to 0 the other threads have a chance to acquire a lock. When A lock is fully released, state returns to 0, and then notifies the queue thread wake Node B, B can compete with the lock again. Of course, if there are threads behind the B-threaded C, C thread continues to sleep, unless B performs over, notified C thread. Note that when a node thread is awakened and made lock, the corresponding node is removed from the queue. 
 

Unfair lock model

If you already know a fair model of speaking in front of the lock, then the lock model it is very unfair easy to understand. After executing the thread A, thread B to awaken it takes time, and even after thread B woke up again compete lock, so if the switching process in which, to a thread C, then C is likely to get the thread to lock if C to obtain a lock, B can only continue obediently dormant. There is no longer drawing illustrates. 
 

Other knowledge

java5 and added to a contract, java.util.concurrent, which offers a variety of tools concurrency by this toolkit, you can achieve very powerful multi-threaded operating them in java. For each java siege lion, I think it is very important to understand the function of this package. Although not do one step, but slowly learn, sink in the heart, always slow to comprehend the essence of java multithreaded programming. 

Q. This story reprinted from other blog, the original address: https: //blog.csdn.net/yanyan19880509/article/details/52345422/

 

2. Why reentrant lock?

  ReentrantLock is a reentrant mutex (/ exclusive) locks , also known as "exclusive lock."

ReentrantLock achieved by custom lock queue synchronizer (AQS-AbstractQueuedSychronized, is the key to the lock) acquire and release.

It can completely replace the synchronized keyword. Early versions of JDK 5.0, the performance is much better than synchronized, but the beginning of JDK 6.0, JDK synchronized done a lot of optimization, making the gap between the two is not significant.

"Exclusive" , that is, at the same time, only one thread to acquire the lock, and the other thread can acquire the lock in sync queue waiting, only to acquire the lock thread to release the lock, subsequent threads to be able to acquire the lock.

"Reentrant" , is to support the re-entry of the lock, which indicates that the lock is capable of supporting a thread locking duplication of resources.

The lock also get support when the lock equity and non-equity of choice. "Fair" means "different thread acquires the lock mechanism is fair" and "unfair" means "different thread acquires the lock mechanism of non-fair."

2,1 interrupt response (lockInterruptibly)

  For synchronized, if a thread is waiting for the lock, then the result of only two cases, continue to get the lock, or thread to keep waiting.

The use of re-entry lock, offers another possibility, which is the thread can be interrupted . That is in the process of waiting for the lock, the program can eliminate the need for locks as needed.

The following example, produced a deadlock, but thanks to the lock is interrupted, finally solved the deadlock:

Copy the code

public class IntLock implements Runnable{
    public static ReentrantLock lock1 = new ReentrantLock();
    public static ReentrantLock lock2 = new ReentrantLock();
    int lock;
    /**
     * Control locking sequence, a deadlock
     */
    public IntLock(int lock) {
        this.lock = lock;
    }
    public void run() {
        try {
            if (lock == 1) {
                lock1.lockInterruptibly (); // if the current thread is interrupted, then acquire the lock.
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace ();
                }
                lock2.lockInterruptibly();
                System.out.println (Thread.currentThread () getName () + ", is finished!.");
            } else {
                lock2.lockInterruptibly();
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace ();
                }
                lock1.lockInterruptibly();
                System.out.println (Thread.currentThread () getName () + ", is finished!.");
            }
        } catch (InterruptedException e) {
            e.printStackTrace ();
        } finally {
            // query whether the current thread to keep this lock.
            if (lock1.isHeldByCurrentThread()) {
                lock1.unlock();
            }
            if (lock2.isHeldByCurrentThread()) {
                lock2.unlock();
            }
            System.out.println(Thread.currentThread().getName() + ",退出。");
        }
    }
    public static void main(String[] args) throws InterruptedException {
        IntLock intLock1 = new IntLock(1);
        IntLock intLock2 = new IntLock(2);
        Thread thread1 = new Thread(intLock1, "线程1");
        Thread thread2 = new Thread(intLock2, "线程2");
        thread1.start();
        thread2.start();
        Thread.sleep(1000);
        thread2.interrupt (); // interrupt thread 2
    }
}

Copy the code

In the above example, after starting the thread thread1 and thread2, thread1 preempt lock1, then occupied lock2; thread2 contrary, preemption lock2, after accounting lock1. This will form the thread1 wait for each other and thread2.

56 lines of code, main thread is in sleep (sleep) state, two threads at this time is in a state of deadlock, the code line 57 thread2 is interrupted (interrupt), it will give up thread2 application for lock1 while releasing lock2 acquired. This operation led to thread1 to secure lock2, in order to continue execution.

Executing code, the following output:

 

2,2 lock application waits limit (tryLock)

  In addition to waiting for external notification (interrupt operation interrupt) outside the limit of waiting you can do to avoid deadlocks.

  Typically, a thread can not determine why the delay can not get a lock. Perhaps because of a deadlock had perhaps had a hunger. But if given a wait time, so that the thread waiver, then the system is significant. You can use tryLock () method to conduct a limited time to wait.

 

Copy the code

public class TimeLock implements Runnable{
    public static ReentrantLock lock = new ReentrantLock();
    public void run() {
        try {
            if (lock.tryLock(5, TimeUnit.SECONDS)) {
                Thread.sleep(6 * 1000);
            }else {
                System.out.println(Thread.currentThread().getName()+" get Lock Failed");
            }
        } catch (InterruptedException e) {
            e.printStackTrace ();
        }finally {
            // query whether the current thread to keep this lock.
            if (lock.isHeldByCurrentThread()) {
                System.out.println(Thread.currentThread().getName()+" release lock");
                lock.unlock();
            }
        }
    }
    /**
     * In the present embodiment, since the thread holding the lock latch will hold up to six seconds, so that the obtained lock can no longer wait time for another thread 5 seconds, so the lock request fails.
     */
    public static void main(String[] args) {
        TimeLock timeLock = new TimeLock();
        Thread t1 = new Thread(timeLock, "线程1");
        Thread t2 = new Thread(timeLock, "线程2");
        t1.start();
        t2.start();
    }
}

Copy the code

  In the above example, since the thread holding the lock latch will hold up to six seconds, you can not obtain a lock on another thread within the waiting time of 5 seconds, and therefore, the lock request fails.

  ReentrantLock.tryLock () method may run directly without parameters. In this case, the current thread tries to obtain a lock if the lock was not being used by another thread, then lock the application is successful, return true immediately. Otherwise, the application fails, return false immediately, the current thread will not wait. This mode does not cause the thread to wait, so it does not produce a deadlock.

2,3 fair locks

  · By default, the application locks are non-fair. In other words, if the thread 1 and thread 2, are locks to apply for A, then who gets the lock is not certain, is randomly selected by the system waiting in the queue. It's like, people do not line up to buy tickets, ticketing sister can only pick a random person sold him, which is obviously unfair. The fair locks, it will follow the chronological order to ensure first-served basis. Fair lock feature is: no hunger.

  Reentrant lock allows to set its fairness. Constructor as follows:

public ReentrantLock(boolean fair)

Copy the code

public class FairLock implements Runnable{
    public static ReentrantLock fairLock = new ReentrantLock(true);

    public void run() {
        while (true) {
            try {
                fairLock.lock();
                System.out.println(Thread.currentThread().getName()+",获得锁!");
            }finally {
                fairLock.unlock();
            }
        }
    }
    public static void main(String[] args) {
        FairLock fairLock = new FairLock();
        Thread t1 = new Thread(fairLock, "线程1");
        Thread t2 = new Thread(fairLock, "线程2");
        t1.start();t2.start();
    }
}

Copy the code

Test Results:

  1. When the parameter is set to be true: Thread 1 and Thread 2 are alternately printed alternately fair competition

Copy the code

Thread 1, get a lock!
Thread 2, get a lock!
Thread 1, get a lock!
Thread 2, get a lock!
Thread 1, get a lock!
Thread 2, get a lock!
Thread 1, get a lock!
Thread 2, get a lock!
Thread 1, get a lock!
Thread 2, get a lock!
Thread 1, get a lock!
Thread 2, get a lock!
Thread 1, get a lock!
Thread 2, get a lock!
Thread 1, get a lock!

Copy the code

 

  2. When the parameter is set to false: see case 1 can continue to get a thread lock thread 1 execution thread after thread 2 can then repeatedly get; this is the use of a fair non-reentrant lock mechanism priority thread can execute multiple times to get the right

Copy the code

Thread 1, get a lock!
Thread 1, get a lock!
Thread 1, get a lock!
Thread 1, get a lock!
Thread 1, get a lock!
Thread 1, get a lock!
Thread 1, get a lock!
Thread 1, get a lock!
Thread 1, get a lock!
Thread 1, get a lock!
Thread 1, get a lock!
Thread 1, get a lock!
Thread 2, get a lock!
Thread 2, get a lock!
Thread 2, get a lock!
Thread 2, get a lock!

Copy the code

 

  Modify reentrant lock is fair, look at the output, if the fair, the output is always alternating two threads to acquire the lock, if non-equity, output is one thread holding the lock for a long time before it releases the lock, other threads to perform.

  Leads to the second question: Why are fair examples appear locks, lock threads are constantly switching fair, not fair lock occurs when the same continuous thread to acquire a lock?

   What is re-entry (re-entry)?

  Re-entry refers to any thread can acquire the lock again after acquiring the lock without being blocked by locks, to implement this feature need to address the following two questions:

  • Thread reacquire lock: Lock the need to identify whether the thread to acquire the lock for the current thread lock occupied, and if so, successfully acquired again.
  • The final release of the lock. Thread n times a lock is acquired, then the n-th release the lock, the other thread can obtain the lock. The final lock acquisition request to release the lock for the self-energizing counted, the count indicates the number of repeated current lock is acquired and the lock is released, the count decrement, when the count is equal to 0 indicates that the lock has been released successfully.

Non-equity lock-source analysis:

Copy the code

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

Copy the code

  acquireQueued method increases the processing logic acquires synchronization status again: by determining whether the current thread to acquire the lock for the thread, to determine whether the operation was successful acquisition, if the thread acquires the lock request again, then the sync state increase value and returns true, indicating acquiring synchronization status successfully.
Successfully acquired lock thread gets the lock again, only increased the value of the synchronization status, which is required to reduce the synchronization status ReentrantLock value at the time of the release of synchronous state, the lock is released source code as follows:

Copy the code

public void unlock() {
    sync.release(1);
}
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

Copy the code

  If the lock is acquired n times, then the first (n-1) times tryRelease (int releases) method must return false, only the synchronous state is fully released, to return true. This method will be synchronized state is zero as a condition for eventual release, when the synchronization status 0, will occupy a thread set to null, and returns true, indicating a successful release.

By analyzing the acquisition and release, can be explained in two examples above two questions arise: Why ReentrantLock lock capable of supporting a thread locking duplication of resources? Why lock case appears fair, equitable lock threads are constantly switching, rather than a fair lock occurs when the same continuous thread to acquire a lock?

  • Why support repeat lock? Because source with variable c to hold the current lock is acquired many times, so when released, to subtract the variable c, only the variable c is 0, considered eventual release of the lock. So many times you can lock, unlock at the same time must also be the same number of times lock.
  • Why lock unfair situation appears the same thread continuously acquire a lock? tryAcquire process increases the processing logic acquires synchronization status again;

    summary

    Several important methods of ReentrantLock above are summarized as follows:

    • lock (): obtain a lock if the lock is occupied, waiting to enter.
    • lockInterruptibly (): get the lock, but the priority interrupt response.
    • tryLock (): try to get a lock, if successful, immediately returned to true, and vice versa failed to return false. This method does not wait to return immediately.
    • tryLock (long time, TimeUnit unit): within a given period of time trying to get a lock.
    • unLock (): release the lock.

    For its implementation principle, next Bowen detailed analysis, which includes three main elements:

    • Atomic states: a state where atoms CAS (compareAndSetState) operative to store the current lock status, determines whether there is another thread holds the lock.
    • Waiting queue: All requests not to lock the thread goes into the waiting queue waiting. After there is a thread releases the lock, the system will be able to wake up a thread from the waiting queue, to continue working. See: queue synchronizer --AQS (to be updated)
    • Blocking primitives park () and unpark (), used to suspend and resume threads. Not lock the thread will be suspended. About blocking primitives, see: thread blocking tools --LockSupport (to be updated).
Published 776 original articles · won praise 50 · Views 150,000 +

Guess you like

Origin blog.csdn.net/weixin_44018338/article/details/105239720