Lock--lock (bottom) in Java thread concurrency __java

Source: Internet
Author: User
Tags cas prev stub

Connect the article exclusive synchronization state acquisition and release

The synchronization state is obtained by invoking the acquire (int arg) method of the Synchronizer, which is insensitive to interrupts, that is, the thread does not move out of the synchronization queue after the thread acquires the sync state failure, and the following thread is interrupted, and the code example:

Public final void acquire (int arg) {
    if!tryacquire (ARG) &&
    acquirequeued (Addwaiter (node.exclusive), ARG))
    selfinterrupt ();
}

The above code mainly completes the synchronization state obtains, the node constructs, joins the synchronization queue as well as in the synchronization queue spins waits the correlation work, its main logic is: first invokes the custom Synchronizer implementation tryacquire (int arg) method, this method guarantees the thread to obtain the synchronization state safely, If the synchronization state fetch fails, the synchronization node is constructed (exclusive node.exclusive, only one thread at a time can obtain the synchronization state successfully) and the node is added to the tail of the synchronization queue through the Addwaiter (Nodes node) method. Finally, the acquirequeued (node Node,int arg) method is called, which causes the node to get the synchronization state in a "dead loop" manner. If you do not get the thread that is blocking the node, the wakeup of the blocked thread relies mainly on the queue of the predecessor node or the blocked thread being interrupted to implement
Now let's look at the Addwriter and Enq methods of the Synchronizer.

private node Addwaiter (node mode) {node node = new Node (Thread.CurrentThread (), mode);
        Quick attempt to add Node pred = tail at the tail;
            if (pred!= null) {Node.prev = pred;
                if (Compareandsettail (pred, node)) {pred.next = node;
            return node;
        } enq (node);
    return node; Private Node Enq (final node node) {for (;;)
            {Node t = tail;
            if (t = = null) {//Must initialize if (Compareandsethead (new Node ())) tail = head;
                else {node.prev = t;
                    if (Compareandsettail (t, node)) {t.next = node;
                return t; }
            }
        }
    }

The preceding code ensures that nodes can be added by thread security by using the Compareandsettail (node Expect,node update) method. Consider this: if you use a normal LinkedList to maintain the relationship between nodes, when one thread acquires the synchronization state, and the other threads are added to the LinkedList concurrently because of the failure to obtain the sync state by invoking the Tryacquire (int arg) method, LinkedList will not be able to guarantee the correct addition of node, the final result may be the number of nodes is biased, and the order is also confusing. In the Enq (final node node) method, the Synchronizer guarantees the correct addition of the node through a "dead loop" in which the current thread can return only after the node has been set up as the tail node in the "Dead Loop" by the CAs, otherwise the current line
process tries to set continuously. As you can see, the final node node method enq the request to add nodes concurrently through CAs. After the node enters the synchronization queue, it enters a spin process, each node (or every thread) is looking at it in an introspective way, and when the condition is met and the synchronization state is obtained, it is possible to exit from the spin process, or remain in the spin process (and block the node's thread)
Let's take a look at the Acquirequeue method in the Synchronizer

    /** * Acquires in exclusive uninterruptible mode for thread already in * queue.
     Used by the condition wait methods as as as the acquire. * @param node the node * @param arg the acquire argument * @return {@code true} if interrupted while wait
        ing * Final boolean acquirequeued (final node node, int arg) {Boolean failed = true;
            try {Boolean interrupted = false; for (;;)
                {final Node P = node.predecessor ();
                    if (p = = head && tryacquire (ARG)) {Sethead (node); P.next = null;
                    Help GC failed = false;
                return interrupted;
                    } if (Shouldparkafterfailedacquire (p, node) && parkandcheckinterrupt ())
            interrupted = true;
        Finally {if (failed) cancelacquire (node);
  }  } 

In the acquirequeued (final Node node,int Arg) method, the current thread tries to get the sync state in the dead loop, and only the predecessor node is the head node to try to get the sync state, because
1, the header node is the node that was successfully fetched to the synchronization state , and after the thread of the head node releases the synchronization state, it wakes up its successor node, and after the thread of the successor node wakes up, it needs to check whether its predecessor node is the head node.
2, maintain the FIFO principle of the synchronization queue. In this method, the behavior of the node spin to obtain the synchronization state is shown in the figure above

The diagram above is returned from the wait state because a non-first node thread precursor node is out of the queue or interrupted, then checks whether its predecessor is the head node, and if so, tries to get the sync state You can see that the nodes and nodes are basically not communicating with each other during the cycle checking process. Rather, it simply determines whether its predecessor is the head node, so that the release rule of the node conforms to the FIFO and facilitates the processing of premature notifications (premature notification is that a thread that is not the head node of the predecessor node is awakened by interruption). The
exclusive synchronization state fetch process, that is, the acquire (int arg) method invocation process, as shown in the following illustration

The decision condition that the predecessor node is the head node and the ability to get the synchronization state and the thread entering the wait state are the spin processes that obtain the synchronization state. When the synchronization state is successful, the current thread returns from the acquire (int arg) method, and if for the lock, the current thread acquires the lock
the current thread acquires the synchronization state and executes the corresponding logic, the synchronization state needs to be released. Enables subsequent nodes to continue to obtain synchronization status. The synchronization state can be freed by invoking the release (int arg) method of the Synchronizer, which wakes its successor node (which in turn causes the successor node to retry the synchronization state) after releasing the synchronization state
the release code for the Synchronizer is as follows:

  /**
     * releases in exclusive mode.  Implemented by unblocking one or
     * More threads if {@link #tryRelease} returns True.
     * This can is used to implement method {@link Lock#unlock}.
     *
     * @param arg the release argument.  This value is conveyed to
     *        {@link #tryRelease} but is otherwise uninterpreted and
     *        can represent Anyth ing you like.
     * @return The value returned from {@link #tryRelease}
    /Public Final Boolean release (int arg) {
        if (Tryrel Ease (ARG)) {
            Node h = head;
            if (h!= null && h.waitstatus!= 0)
                unparksuccessor (h);
            return true;
        }
        return false;
    }

When the method executes, the successor node thread of the head node is awakened. After analyzing the process of acquiring and releasing the state of exclusive synchronization, it is necessary to make a summary: When the synchronization state is obtained, the Synchronizer maintains a synchronization queue, and the thread that gets the state failure is added to the queue and spins in the queue, and the condition of the queue (or stop spin) is that the predecessor node is the head node and the synchronization state is successfully obtained. When the synchronization state is released, the Synchronizer invokes the tryrelease (int arg) method to release the synchronization state, and then wakes the successor node of the head node to the acquisition and release of the shared synchronization state

The main difference between shared and exclusive access is whether multiple threads can get to the synchronization state at the same time. As an example of read-write for a file, if a program is reading the file, then the write operation for the file is blocked and the read can be performed simultaneously. Write operations require exclusive access to resources, while read operations can be shared access and two different access patterns access to files or resources at the same time.
by calling the Synchronizer's acquireshared (int arg) method to get the synchronization state in a shared way, we look at the acquireshared and doacquireshared methods of the Synchronizer

/** * Acquires in shared mode, ignoring interrupts.  Implemented by *-invoking at least once {@link #tryAcquireShared}, * returning on success. Otherwise the thread is queued, possibly * repeatedly blocking and unblocking, invoking {@link * #tryAcquireShar
     Ed} until success.  * * @param arg the acquire argument. This value is conveyed to * {@link #tryAcquireShared} but is otherwise uninterpreted * and can Rep
     Resent anything you. */public final void acquireshared (int arg) {if (tryacquireshared (ARG) < 0) doacquireshared (AR
    g);
     }/** * Acquires in shared uninterruptible mode. * @param arg the acquire argument */private void doacquireshared (int arg) {final node node = addwaiter (
        node.shared);
        Boolean failed = true;
            try {Boolean interrupted = false; for (;;)
   {final Node P = node.predecessor ();             if (p = = head) {int r = tryacquireshared (ARG);
                        if (r >= 0) {setheadandpropagate (node, r); P.next = null;
                        Help GC if (interrupted) selfinterrupt ();
                        Failed = false;
                    Return } if (Shouldparkafterfailedacquire (p, node) && Parkandchecki
            Nterrupt ()) interrupted = true;
        Finally {if (failed) cancelacquire (node); }
    }

Note: Why the dead loop of the JDK principle is for (;;) Instead of while (1), because while (1) after compilation is the Mov eax,1 Test eax,eax je foo+23h jmp foo+18h,for (;;) After compiling is JMP foo+23h, you can see for (;;) Fewer instructions, no occupation of registers, no judgment jump, more efficient
in the acquireshared (int arg) method, the Synchronizer calls the tryacquireshared (int arg) method to attempt to obtain a sync state. The tryacquireshared (int arg) method returns a value of type int, and when the return value is greater than or equal to 0 o'clock, it indicates that the synchronization state can be obtained. Therefore, the condition that the
tryacquireshared (int arg) method returns a value greater than or equal to 0 is the result of successfully acquiring the synchronization state and exiting the spin during a shared-access spin. As you can see, during the spin of the doacquireshared (int arg method), if the current node's predecessor is a header node, try to get the sync state, if the return value is greater than or equal to 0, indicating that the fetch synchronization state succeeds and exits from the spin process. As with an exclusive, shared access also frees the synchronization state by invoking the releaseshared (int arg) method to release the synchronization state, which is as follows

   /**
     * Releases in shared mode.  Implemented by unblocking one or more
     * threads if {@link #tryReleaseShared} returns True.
     *
     * @param arg the release argument.  This value is conveyed to
     *        {@link #tryReleaseShared} but is otherwise uninterpreted
     * and        can represent anything.
     * @return The value returned from {@link #tryReleaseShared}
    /Public Final boolean releaseshared (int arg) {
  if (tryreleaseshared (ARG)) {
            doreleaseshared ();
            return true;
        }
        return false;
    }

After releasing the synchronization state, the method wakes up subsequent nodes in the waiting state. For concurrent components that can support multiple threads at the same time (such as Semaphore), it differs primarily from exclusivity in that the tryreleaseshared (int arg) method must ensure that the synchronization state (or number of resources) is thread-safe-free, typically through loops and CAs. Because the operation that releases the synchronization state comes from multiple threads at the same time. Exclusive timeout past sync State

By calling the Doacquirenanos (int arg,long nanostimeout) method of the Synchronizer, the synchronization state can be timed out, that is, the synchronization state is obtained within a specified time period, or false if the synchronization state is obtained.
before Java 5, when a thread is blocked outside the synchronized when it acquires a lock, the interrupt flag bit of the thread is modified, but the thread remains blocked on the synchronized and waits for the lock to be acquired. In Java 5, the Synchronizer provides a acquireinterruptibly (int arg) method that, when waiting for a sync state, returns immediately if the current thread is interrupted and throws Interruptedexception
The timeout acquisition synchronization state process can be viewed as a "enhanced version" of the response interrupt acquisition synchronization state process, and the Doacquirenanos (int arg,long nanostimeout) method increases the feature of timeout acquisition on the basis of supporting interrupt response. For timeouts, the primary need is to calculate the interval nanostimeout to sleep, in order to prevent premature notification, the
Nanostimeout formula is: Nanostimeout-=now-lasttime, where now is the current wake-up time, Lasttime is the last wake-up time, if Nanostimeout is greater than 0 to indicate that the timeout period is not up, you need to continue to sleep nanostimeout nanosecond, and conversely, the method code is

as follows:

  /** * Acquires in exclusive timed mode.
     * @param arg the acquire argument * @param nanostimeout max wait time * @return {@code true} if acquired * Private Boolean Doacquirenanos (int arg, long nanostimeout) throws Interruptedexception {long
        Lasttime = System.nanotime ();
        Final node node = addwaiter (node.exclusive);
        Boolean failed = true; try {for (;;)
                {final Node P = node.predecessor ();
                    if (p = = head && tryacquire (ARG)) {Sethead (node); P.next = null;
                    Help GC failed = false;
                return true;
                if (nanostimeout <= 0) return false;
                    if (Shouldparkafterfailedacquire (p, node) && nanostimeout > Spinfortimeoutthreshold)
   Locksupport.parknanos (this, nanostimeout);             Long now = System.nanotime ();
                Nanostimeout-= Now-lasttime;
                Lasttime = Now;
            if (thread.interrupted ()) throw new Interruptedexception ();
        Finally {if (failed) cancelacquire (node); }
    }

The method attempts to obtain a synchronization state when the predecessor node of the node is a head node during the spin process, and returns from the method if the success is obtained, which is similar to the process of the exclusive synchronization fetch, but differs in the processing of failed synchronization state acquisition. If the current thread acquires a sync state failure, it is judged whether the timeout (Nanostimeout is less than or equal to 0 means
has timed out), and if there is no timeout, recalculate the timeout interval nanostimeout, The current thread is then waited for Nanostimeout (when the timeout has been set, the thread returns from the Locksuport.parknanos (Object Blocker,long Nanos) method). If the nanostimeout is less than or equal to Spinfortimeoutthreshold (1000 nanoseconds), the thread is not timed out to wait, but rather into a fast spin process. The reason is that very short timeout wait can not be very accurate, if the time to wait for overtime, instead of the Nanostimeout timeout from the overall performance is not accurate. Therefore, in a very short timeout scenario, the Synchronizer will enter an unconditional fast spin.
the process of obtaining synchronization status by exclusive timeout is as follows:
custom sync Components

Design a synchronization tool: The tool at the same time allows up to two threads to access at the same time, access to more than two threads will be blocked, and we will name this synchronization tool Twinslock.
First, determine the access mode. Twinslock can support multiple threads of access at the same time, which is clearly shared access, so you need to use the acquireshared (int args) method provided by the Synchronizer and shared-related methods. This requires Twinslock to override the tryacquireshared (int args) method and the tryreleaseshared (int args) method in order to ensure that the method of obtaining and releasing the shared synchronization state of the Synchronizer is performed.
Second, define the number of resources. Twinslock allows simultaneous access of up to two threads at the same time, indicating that the number of synchronized resources is 2, so that the initial state status is 2, when a thread is fetched, the status is reduced by 1, and the thread is released, the status plus 1, and the legal range of states is 0, 1 and 2 0 indicates that two threads are currently acquiring the synchronization resource, and there are other threads fetching the synchronization state and the thread can only be blocked. When synchronizing state changes, you need to use the Compareandset (int expect,int Update) method to do atomic protection.
Finally, combine the custom Synchronizer.

public class Twinslock implements Lock {private final sync sync = new sync (2); @SuppressWarnings ("serial") private static final class Sync extends abstractqueuedsynchronizer{sync (int count
            {if (count <0) {throw new IllegalArgumentException ("Count must larger than zero");
        } setstate (count); public int tryacquireshared (int reducecount) {for (;;)
                {int current = GetState ();
                int newcount = Current-reducecount;
                if (Newcount < 0 | | | compareandsetstate (current, Newcount)) {return newcount; }} public boolean tryreleaseshared (int returncount) {for (;;)
                {int current = GetState ();
                int newcount = Current+returncount;
                if (Compareandsetstate (current, Newcount)) {return true;
       }     @Override public void Lock () {sync.acquireshared (1); @Override public void lockinterruptibly () throws Interruptedexception {//TODO auto-generated Method St
    UB} @Override public boolean trylock () {//TODO auto-generated a stub return false; @Override public boolean Trylock (long, Timeunit unit) throws Interruptedexception {/
    /TODO auto-generated method stub return false;
    @Override public void Unlock () {sync.tryreleaseshared (1);
    @Override public Condition newcondition () {//TODO auto-generated a stub return null; }
}

Test class

public class Twinslocktest {public static void main (string[] args) {final lock lock = new Twinslock (); Class Worker extends thread{public void run () {for (;;)
                    {Lock.lock ();
                        try {thread.sleep (1000);
                        System.out.println (Thread.CurrentThread (). GetName ());
                    Thread.Sleep (1000);
                    catch (Interruptedexception e) {e.printstacktrace ();
                    }finally{Lock.unlock ();
            for (int i=0;i<10;i++) {worker worker = new Worker ();
            Worker.setdaemon (TRUE);
        Worker.start ();
                for (int i=0;i<10;i++) {try {thread.sleep (1000);
            SYSTEM.OUT.PRINTLN ("----");
 catch (Interruptedexception e) {               E.printstacktrace (); }
        }
    }
}

The results of the operation are as follows:

Thread-0
Thread-1
----
----
----
Thread-1
Thread-0
----
----
Thread-1
Thread-0
----
----
Thread-0
Thread-1
----
Thread-1
Thread-0
----
----

If the limit is changed to 5, the result is as follows:

Thread-2
Thread-3
Thread-0
----
Thread-1
Thread-4
----
----
Thread-3
Thread-2
Thread-0
Thread-4
Thread-1
----
----
Thread-0
Thread-3
Thread-4 Thread-2
Thread-1
----
Thread-4
Thread-3
Thread-2
Thread-0
----

Because the concurrency is different, the number of threads is different, but within 5

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.