Introduction and principle analysis of Abstractqueuedsynchronizer

Source: Internet
Author: User

Transfer from http://ifeve.com/introduce-abstractqueuedsynchronizer/

Provides an infrastructure based on FIFO queues that can be used to build locks or other related synchronization devices. The Synchronizer (hereinafter referred to as the Synchronizer) uses an int to represent the state and expects it to be the basis for most of the synchronization requirements. The method used is inheritance, and the subclass manages its state by inheriting the Synchronizer and needing to implement its method, which is managed by manipulating the state in a manner similar to acquire and release. However, the manipulation of state in a multithreaded environment must ensure atomicity, so the subclass's grasp of the state requires the following three methods provided by this Synchronizer to operate on the state:

    • Java.util.concurrent.locks.AbstractQueuedSynchronizer.getState ()
    • java.util.concurrent.locks.AbstractQueuedSynchronizer.setState (int)
    • java.util.concurrent.locks.AbstractQueuedSynchronizer.compareAndSetState (int, int)

Sub-class recommendation is defined as the internal class of the custom Synchronizer, the Synchronizer itself does not implement any synchronous interface, it simply defines a number of methods such as acquire to be used. The Synchronizer can be either an exclusive mode or a shared mode, and when it is defined as an exclusive mode, the other threads are prevented from acquiring it, and the shared mode succeeds for multiple thread fetching.


Synchronizer is the key to implement the lock, using the Synchronizer to implement the semantics of the lock and then aggregating the Synchronizer in the implementation of the lock. It can be understood that the lock API is intended for the consumer, which defines the public behavior of interaction with the lock, and each lock needs to complete a specific operation through these behaviors (for example: can allow two threads to lock, excluding more than two threads), but the implementation is based on the Synchronizer to complete The Synchronizer is oriented to thread access and resource control, which defines whether the thread can get the resources and queue the threads. The lock and Synchronizer are a good way to isolate the areas of concern, and in strict sense, the Synchronizer can be applied to other synchronization facilities (including locks) other than locks.
The beginning of the Synchronizer mentions that its implementation relies on a FIFO queue, so the element node in the queue is the container that holds the thread reference and the thread state, and each thread accesses the Synchronizer as a node in the queue. Node's main contains the following member variables:

Node {    int waitstatus;    Node prev;    Node Next;    Node Nextwaiter;    Thread thread;}

The above five member variables are primarily responsible for saving the thread reference of the node, the predecessor and successor of the synchronous Wait queue (hereinafter referred to as the sync queue), and also the synchronization status.

property name description
int W Aitstatus represents the state of the node. It contains a status of
  1. CANCELLED, a value of 1, indicating that the current thread is canceled,
  2. SIGNAL, and a value of 1, indicating that the thread that the successor of the current node contains needs to run, that is, Unpark;
  3. CONDITION, which is a value of 2, indicating that the current node is waiting for CONDITION, which is in the CONDITION queue;
  4. PROPAGATE, which is a value of-3, indicating that subsequent acquireshared in the current scene can be executed;
  5. A
  6. value of 0 indicates that the current node is in the sync queue and waits for a lock to be acquired.
node prev A precursor node, such as the current node being canceled, requires a precursor node and a successor to complete the connection.
node next successor.
node Nextwaiter stores the successors in the condition queue.
thread thread The current thread when it is queued.

The node becomes the basis for the sync queue and condition queue build, and the sync queue is included in the Synchronizer. The Synchronizer has three member variables: the head of the sync queue, the tail node of the sync queue tail, and the status state. For lock acquisition, the request forms a node, mounts it at the tail, and the lock resource's transfer (release and fetch) starts backwards from the head. For the status state of the Synchronizer maintenance, the fetch of multiple threads to it will result in a chain-structured structure.

API description

When implementing a custom Synchronizer, you use the GetState (), SetState (), and Compareandsetstate () methods provided by the Synchronizer to manipulate the state of the transition.

Method name Describe
protected Boolean tryacquire (int arg) Row it to get this state. The implementation of this method requires querying whether the current state is allowed to be fetched, and then fetching (using Compareandsetstate) state.
protected Boolean tryrelease (int arg) Release status.
protected int tryacquireshared (int arg) The shared mode gets the state.
protected Boolean tryreleaseshared (int arg) The state is released in shared mode.
Protected Boolean isheldexclusively () In exclusive mode, the state is occupied.

Implementing these methods must be non-blocking and thread-safe, and it is recommended to use the Synchronizer's parent class Java.util.concurrent.locks.AbstractOwnableSynchronizer to set the current thread.
Start mentioning that the Synchronizer is internally based on a FIFO queue, and the following pseudocode can be represented for the acquisition and release of an exclusive lock.
Gets an exclusive lock.

while (get Lock) {if (get to) {exit While loop} else {if (current thread does not enter queue) {then into queue}}}

Releases an exclusive lock.

If (release succeeded) {Delete header node activates the successor of the original head nodes}
Example

Here's an example of an exclusive lock that gives you a deep understanding of how the Synchronizer works, and only mastering how the Synchronizer works can give you more insight into other concurrent components.
The implementation of an exclusive lock that only one thread can acquire to a lock at a time.

Class Mutex implements Lock, java.io.Serializable {//inner class, custom Synchronizer private static class Sync extends Abstractqueuedsync     Hronizer {//is in the occupied State protected Boolean isheldexclusively () {return getState () = = 1; }//When the state is 0 get lock public boolean tryacquire (int acquires) {Assert acquires = = 1;//Otherwise Unused I         F (compareandsetstate (0, 1)) {Setexclusiveownerthread (Thread.CurrentThread ());       return true;     } return false;       }//Release lock, set the status to 0 protected boolean tryrelease (int releases) {assert releases = = 1;//Otherwise unused       if (getState () = = 0) throw new illegalmonitorstateexception ();       Setexclusiveownerthread (NULL);       SetState (0);     return true;   }//Returns a Condition, each Condition contains a Condition queue Condition newcondition () {return new Conditionobject ();}   }//Only need to agent the operation to the sync can be private final sync sync = new sync ();   public void Lock () {sync.acquire (1);} PublIC Boolean Trylock () {return sync.tryacquire (1);}   public void Unlock () {sync.release (1);}   Public Condition newcondition () {return sync.newcondition ();}   public Boolean isLocked () {return sync.isheldexclusively ();}   public Boolean hasqueuedthreads () {return sync.hasqueuedthreads ();}   public void lockinterruptibly () throws Interruptedexception {sync.acquireinterruptibly (1); public boolean Trylock (long timeout, timeunit unit) throws Interruptedexception {return Sync.tryacquirenano   S (1, Unit.tonanos (timeout)); } }

You can see that the mutex proxies the lock interface to the implementation of the Synchronizer.
After the consumer constructs the mutex, call lock to get the lock and call unlock to unlock it. The following is an example of a mutex, which analyzes the implementation logic of these synchronizers in detail.

Implementation analysis Public final void acquire (int arg)

This method acquires the lock in an exclusive manner, is insensitive to interrupts, and completes the synchronized semantics.

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

The above logic mainly includes:
1. Try to get (call Tryacquire change state, need to ensure atomicity);
Using the method provided by the Synchronizer in the Tryacquire method, the Compareandset guarantees that only one thread can successfully modify the state, and the thread that has not successfully modified will enter the sync queue.
2. If not acquired, constructs the current thread as a node and joins the sync queue;
Each thread that enters the queue is a node, which forms a two-way queue, similar to the CLH queue, so that the communication between threads is limited to a smaller size (that is, about two nodes).
3. Try again to get, if not get to then the current thread from the thread scheduler off, into the waiting state.
Use Locksupport to unpark the current thread, as described in more detail in the Locksupport follow-up.

private node Addwaiter (node mode) {node node = new Node (  Thread.CurrentThread (), mode);//fast attempt to add node pred = tail;if (pred! = null) in the tail {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 Initializeif (Compareandsethead (New Node ())) tail = head;} else {Node.prev = T;if ( Compareandsettail (t, node)) {t.next = Node;return t;}}} 

The above logic mainly includes:
1. Constructs node using the current thread;
for a node to do is to point the node precursor node to the tail node (Current.prev = tail), the tail node points to it (tail = current), The successor node of the original tail node points to it (T.next = current) and these operational requirements are atomic. The above operation is ensured by the setting of the tail node, that is, the compareandsettail to complete.
2. First try to add at the end of the queue;
if the tail node already has it, then do the following:
(1) Assign the reference t to the tail node, and
(2) To update the node's precursor node to the tail node (Current.prev = tail);
(3) If the tail node is T, then the tail node is set to that node (tail = current, atomic update); The successor of
(4) T points to the active node (T.next = present).
Note that the 3rd requirement is atomic.
This allows the queue to be completed with the shortest path O (1) effect, which is one way to maximize the cost reduction.
3. If the end of the queue is added failed or is the first queued node.
If it is a 1th node, that is, the sync queue is not initialized, then it will go into the Enq method, there may be multiple threads, or the thread that is not successfully queued in Addwaiter will enter Enq this method. The
can see that the logic of ENQ is to ensure that the incoming node will be added to the sync queue in sequence, and the steps are as follows:
(1) If the tail node is empty, then atomically assigns a head node and points the tail node to the head node, which is initialized;
(2) It then repeats the work done in Addwaiter, but in a loop of while (true) until the current node is enqueued. After the
enters the sync queue, the next step is to get the lock, or access control, and only one thread can continue to run at the same time while the other goes into the waiting state. Each thread is a separate individual, and they are self-examining, and the thread can continue to run when the condition is fulfilled (its predecessor is the head node and the Atom acquires the state).

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 gcfailed = False;return interrupted;}                    if (Shouldparkafterfailedacquire (p, node) &&                Parkandcheckinterrupt ()) interrupted = true; }} finally {if (failed) cancelacquire (node);}} 

The above logic mainly includes:
1. Get the precursor node of the current node;
The predecessor node of the current node needs to be acquired, and the head node corresponds to the current station has a lock and is running.
2. The current drive node is the head junction and is able to obtain the state, representing the current node occupies the lock;
If the above conditions are met, then the representative can occupy the lock, according to the meaning of the node to the lock, set the head node as the current point.
3. Otherwise enter the waiting state.
If the current node is not running, then the current thread is removed from the thread scheduler, that is, into the wait state.
Here is a summary for acquire:
1. Maintenance of the State;
Need to maintain a state (int type) when locked, and the operation on State is atomic and non-blocking, manipulating the state through the method provided by the Synchronizer to state access, and using Compareandset to ensure atomic modification.
2. Access to the state;
Once the state has been successfully modified, the current thread or node is set as the head node.
3. Maintenance of the sync queue.
Stop the thread scheduler from scheduling the current node thread when the condition is not met in the process of obtaining a resource failure (neither the precursor node nor the head node, nor the resource being fetched) goes to sleep.
At this point, the introduction of a release of the problem, that is, the node in sleep or the thread to get notification of the key, is the precursor of the notification, and this process is to release, release will notify its successor node from sleep back to prepare to run.
The following flowchart basically describes the process that a acquire needs to experience:

As shown in the criteria in which the decision exits the queue, determining whether the condition satisfies and sleeps the current thread is the process of completing the spin spin.

Public final Boolean release (int arg)

In the implementation of the Unlock method, the release method of the Synchronizer is used. Instead of calling acquire in the previous acquire method, it is guaranteed to acquire a lock (a successful acquisition state), while release indicates that the state is set back, that is, releasing the resource, or releasing the lock.

Public final Boolean release (int arg) {if (Tryrelease (ARG)) {Node h = head;if (h! = null && h.waitstatus!) = 0) Unparksuccessor (h); return true;} return false;}

The above logic mainly includes:
1. Attempt to release the state;
Tryrelease can ensure that the status of the atomization is set back, of course, need to use compareandset to ensure. If the release state succeeds, it will enter the wake-up process of the subsequent node.
2. Wakes the threads that are contained by the successor node of the current node.
Wake the thread in hibernation by Locksupport the Unpark method to continue to acquire state.

private void Unparksuccessor (node node) {//sets the state to synchronous state int ws = NODE.WAITSTATUS;IF (ws < 0) Compareandsetwaitstatus ( node, WS, 0); Gets the successor of the current node, if it satisfies the state, then wakes up//if it is not satisfied, searches for the node that meets the requirements from the tail and wakes it up node s = node.next; if (s = = NULL | | s.waitstatus > 0) {s = null;for (node t = tail; t! = null && t! = Node; t = T.prev ) if (t.waitstatus <= 0) s = t;} if (s! = null) Locksupport.unpark (s.thread);}

The above logic mainly includes, the method takes out the next reference of the current node and then wakes its thread (node), when only one or a reasonable number of threads are awakened, the awakened thread continues to fetch and scramble for the resource.
Review the acquisition and release process for the entire resource:
When acquired, maintains a sync queue, each node is a thread in the spin, and based on whether it is the successor of the first node and can obtain resources;
When you release, you just need to return the resource and then notify the successor and wake it up.
It is important to note that the maintenance of the queue (the replacement of the first node) is done by the consumer (acquisition), which means that the node is set as the first node when the spin exit condition is met.

protected Boolean tryacquire (int arg)

Tryacquire is the method that a custom Synchronizer needs to implement, that is, the acquisition state of a custom Synchronizer's non-blocking atomization, which is not provided by synchronized if the lock method is generally used in the Trylock implementation of lock.

Public final void acquireinterruptibly (int arg)

This method provides the ability to acquire state, of course, in the case of the inability to get a state to enter the sync queue queued, which is similar to acquire, but unlike acquire is that it can be in the external to the current thread interruption of the time before the end of the operation to get the state, in other words, When a lock is acquired like a synchronized, the outside world is able to interrupt the current thread, and the operation that acquires the lock responds to the interrupt and returns early. When a thread is in a synchronized block or is doing a synchronous I/O operation, the thread is interrupted, and the thread's interrupt identity bit is set to true, but the thread continues to run.
If the lock resource is suddenly destroyed when it acquires a lock that is implemented through a network interaction, then using the Acquireinterruptibly method allows the thread that is trying to acquire the lock to return ahead of time. This feature of the Synchronizer is implemented by the Lockinterruptibly method in the lock interface. According to the semantics of lock, when interrupted, lockinterruptibly will throw interruptedexception to inform the consumer.

public final void acquireinterruptibly (int arg) throws interruptedexception {if (thread.interrupted ()) throw new Interruptedexception (); if (!tryacquire (ARG)) Doacquireinterruptibly (ARG);} private void doacquireinterruptibly (int arg) throws Interruptedexception {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 gcfailed = False;return;} Detect interrupt Flag bit if (Shouldparkafterfailedacquire (p, node) &&parkandcheckinterrupt ()) throw new Interruptedexception ();}} Finally {if (failed) cancelacquire (node);}} 

The above logic mainly includes:
1. Detect if the current thread is interrupted;
Determines the interrupt flag bit for the current thread and, if it has been interrupted, throws the exception directly and sets the interrupt flag bit to false.
2. Try to get the status;
Call Tryacquire to get the status, if successful, and return.
3. Construct the node and join the sync queue;
After the fetch state fails, the current thread reference is constructed as a node and joined to the sync queue. The way to exit the queue is similar to acquirequeued in a non-disruptive scenario, where the head node is its predecessor and is able to get to the state, that is, it can run, of course, to set this node as a header, indicating that it is running.
4. Interrupt detection.
Interrupt detection occurs each time it is awakened, and if the current thread is found to be interrupted, throw interruptedexception and exit the loop.

Private Boolean Doacquirenanos (int arg, long nanostimeout) throws Interruptedexception

This method provides a call with a fetch state with a timeout function, returns False if no state is obtained within the specified Nanostimeout, and returns True instead. This method can be seen as an upgraded version of acquireinterruptibly, which increases the time-out control based on whether the judgment is interrupted.
For the implementation of this part of time-out control, the main need to calculate the sleep delta, that is, the interval value. The interval can be expressed as Nanostimeout = original Nanostimeout–now (current time) + Lasttime (time recorded before sleep). If Nanostimeout is greater than 0, you also need to sleep on the current thread, or False if the reverse is true.

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 gcfailed = False;return true;} if (nanostimeout <= 0) return false; if (Shouldparkafterfailedacquire (p, node) && nanostimeout > spinfortimeoutthreshold) Locksupport.parknanos (this, nanostimeout); Long now = System.nanotime ();//calculation time, current time minus the time before sleep to get sleep time, and then//the original time-out time minus, Get the time that should also sleep nanostimeout-= Now-lasttime;lasttime = Now;if (thread.interrupted ()) throw new Interruptedexception ();}} Finally {if (failed) cancelacquire (node);}} 

The above logic mainly includes:
1. Join the sync queue;
Constructs the current thread as node nodes to join the sync queue.
2. The condition satisfies the direct return;
Exit criteria, if the precursor node is a header and is successfully acquired to the state, then set itself as the head node and exit, returning true, that is, the lock was acquired before the specified nanostimeout.
3. Get state failure hibernation for a while;
Specifies that the current thread sleeps for some time by Locksupport.unpark.
4. Calculate the time to sleep again;
After waking the thread, the calculation still needs to sleep for the time, which is expressed as Nanostimeout = original Nanostimeout–now (current time) + Lasttime (time recorded before sleep). One of the now–lasttime represents the duration of this sleep.
5. Determination of sleep time.
After waking the thread, the calculation still needs to hibernate the time, and no blocking attempts to get the state again, if it fails to see if its nanostimeout is greater than 0 if it is less than 0, then returns the full timeout, without acquiring the lock. If the nanostimeout is less than or equal to 1000L nanoseconds, it enters a fast spin process. So does fast spin cause processor resources to be strained? The result is no, measured, overhead looks small, almost negligible. Doug Lea should measure the extra overhead of switching on the thread scheduler, so that in a short time of 1000 nanoseconds, a fast spin state is allowed for the current threads, and if this is the case then the Nanostimeout's acquisition time becomes less accurate.
The above procedure can be as follows:

The above diagram can be understood to add a timeout control logic based on the need to queue up a similar acquisition state. The time of each timeout is the time remaining for the current time-out minus the time of sleep, and on the basis of this time-out is judged, if it is greater than 0 then continue to sleep (wait), you can see that this time-out version of the acquisition state is just an approximate time-out of the acquisition State, So any call with a timeout would have a basic result similar to a given timeout.

Public final void acquireshared (int arg)

Call this method to get the state in shared mode, which differs from the previous exclusive mode. Take a file view as an example, if a program is reading it, then at this point, the write operation of the file is blocked, instead, at this moment another program to do the same read operation is possible. If a program is writing to it, all of the read and write operations are blocked at this point until the program completes the write operation.
For example, read and write scenarios that describe shared and exclusive access patterns, as shown in:

, the red is blocked, and the green representative can pass.

Public final void acquireshared (int arg) {if (tryacquireshared (ARG) < 0) doacquireshared (ARG);} private void Doacqu ireshared (int arg) {final node node = addwaiter (node.shared); Boolean failed = true; try {Boolean interrupted = false; F or (;;) {final Node P = node.predecessor (); if (p = = head) {int r = tryacquireshared (arg); if (r >= 0) {Setheadandpropaga TE (node, R);p. Next = null; Help Gcif (interrupted) selfinterrupt (); failed = False;return;}} if (Shouldparkafterfailedacquire (p, node) &&parkandcheckinterrupt ()) interrupted = true;}} Finally {if (failed) cancelacquire (node);}}

The above logic mainly includes:
1. Try to get shared status;
Call tryacquireshared to get the shared state, which is non-blocking and returns immediately if the fetch succeeds, which means that the acquisition of the shared lock succeeds.
2. Get failed to enter sync queue;
When the share state fails, the current moment may be that the exclusive lock is held by another thread, then the current thread is constructed into a node (shared mode) into the sync queue.
3. In the loop to determine the exit queue conditions;
This is similar to the exit queue condition for an exclusive lock acquire if the predecessor node of the current node is a header and acquires a shared state success.
4. Access to shared status is successful;
The main difference between the exit queue and the exclusive lock is the behavior after the shared state succeeds, and if the shared state succeeds, it determines whether the successor is a shared mode, and if it is a shared mode, it wakes directly, that is, simultaneously firing multiple threads concurrently.
5. Failed to get share status.
Go into hibernation by using Locksupport to remove the current thread from the thread scheduler.
For the above logic, the notification process between the nodes is as follows:

, the green represents a shared node, and the notification and wake-up operations between them are performed when the predecessor node acquires the state, red indicates the exclusive node, and its wake must depend on the release of the precursor node, that is, the releasing operation, which can be seen if the exclusive node in the diagram is to run, You must wait for the previous shared node to release the state. If the exclusive node acquires a state, subsequent exclusive and shared fetches are blocked.

Public final Boolean releaseshared (int arg)

This method is called to release the shared state, and each time the shared state is acquired, the state is acquireshared, and the state is also freed when the shared lock is released. For example, a synchronization tool that restricts a certain number of accesses is shared each time, but if a certain amount is exceeded, the subsequent fetch is blocked, and the blocked fetch can be run only if the previously acquired consumer releases the state.

Public final Boolean releaseshared (int arg) {if (tryreleaseshared (ARG)) {doreleaseshared (); return true;} return false;}

The logic above is to invoke the Tryreleaseshared method of the Synchronizer to release the state, and wake its successors in the Doreleaseshared method at the same time.

An example

After analyzing the implementation level of the Synchronizer Abstractqueuedsynchronizer, we have an example to deepen our understanding of the Synchronizer:
Design a synchronization tool that, at the same time, only two threads can access in parallel, and other threads that exceed the limit enter a blocking state.
For this requirement, you can use the Synchronizer to complete one such setting, define an initial state, 2, a thread to get so minus 1, a thread to release that plus 1, the correct range of states between [0,1,2] three, when at 0 o'clock, Represents that a new thread can only enter a blocking state when it acquires a resource (note that the CAS is required for atomic protection at any time for state changes). Because of the number of resources more than 1, and can have two threads to occupy resources, it is necessary to implement the tryacquireshared and Tryreleaseshared methods, here thank Luoyuyou and colleague Xiaoming, has modified the implementation.

public class Twinslock implements Lock {private final syncsync= new sync (2);p rivate static final class Sync extends Abstra Ctqueuedsynchronizer {private static final longserialversionuid= -7889272986162341211l; Sync (int count) {if (count <= 0) {throw new IllegalArgumentException ("Count must large 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 = getState (), int newcount = current + returncount;if (compareandsetstate (current, Newcount)) {return true;}} }}public void Lock () {sync.acquireshared (1);} public void lockinterruptibly () throws Interruptedexception {sync.acquiresharedinterruptibly (1);} public Boolean Trylock () {return sync.tryacquireshared (1) >= 0;} public Boolean Trylock (long time, Timeunit unit) throws Interruptedexception {RETUrn Sync.tryacquiresharednanos (1, Unit.tonanos (time));} public void Unlock () {sync.releaseshared (1);} @Overridepublic Condition newcondition () {return null;}}

Here we write a test to verify that the Twinslock is working and is expected to work correctly.

public class Twinslocktest {@Testpublic void Test () {final lock lock = new Twinslock (), class Worker extends Thread {public void Run () {while (true) {lock.lock (); try {thread.sleep (1000L); System.out.println (Thread.CurrentThread ()); Thread.Sleep (1000L);} catch (Exception ex) {} finally {Lock.unlock ();}}}} for (int i = 0; I &lt; i++) {worker w = new Worker (); W.start ();} New Thread () {public void run () {while (true) {try {thread.sleep (200L); System.out.println ();} catch (Exception ex) {}}}}.start (); try {thread.sleep (20000L);} catch (Interruptedexception e) {e.printstacktrace ();}}}

The logic of the above test cases mainly includes:
? 1. Print Thread
The worker prints its own thread between two sleep, and if only two threads are accessible at a time, the printed content will appear in pairs.
? 2. Separating threads
The continuous printing of line breaks can make the worker's output look more intuitive.
The result of the test was that only two threads were able to get to the lock and finish printing at a single moment, and the appearance was that the printed content appeared in pairs.

Introduction and principle analysis of Abstractqueuedsynchronizer

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.