Java Lock-another Implementation of Synchronization

Source: Internet
Author: User

Java Lock-another Implementation of Synchronization

Usually Junior programmers like to use the synchronized keyword to implement synchronization mechanism. The reason is simple. It is simple to use. We don't need to consider more details and have low requirements on programmers. Here we will introduce another synchronization method implemented by Lock. Obviously, the Lock method can make the program concurrency more efficient and flexible, and it has higher requirements on programmers.

Lock Method
public interface Lock {    void lock();    void lockInterruptibly() throws InterruptedException;    boolean tryLock();    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;    void unlock();    Condition newCondition();}

From the Lock source code, we can see that it is an interface.

Lock () if the lock can be obtained, it will return. If the lock cannot be obtained, it will remain waiting for the lock to be obtained.

LockInterruptibly () supports thread interruption. If the thread is interrupted, it will not compete by default. This is more scalable than synchronizing code and synchronization methods.

TryLock () if the lock can be obtained, true is immediately returned; otherwise, false is returned.

TryLock (long time, TimeUnit unit) returns true if it can be locked. If there are two cases, it is set to false:
First, the thread is interrupted;
Second, the time is up.

Unlock () releases a lock.

Condition newCondition () introduces Condition with the intention of two methods:

A. the read and write capabilities are available for a shared resource. If the read thread or write thread obtains the Lock power, it is able to enter, but if there is no content in it, reading is useless, if the space is full, you cannot write the data. You have to determine if the thread is waiting;

B. provides a communication mechanism between multiple threads, similar to the principles of wait () and nofity.

Introduction to lock-related concepts 1. reentrant locks

Reentrant locks, also known as recursive locks, indicate that after the outer function of the same thread acquires the lock, the inner recursive function still has the code to obtain the lock, but it is not affected.

In the JAVA environment, ReentrantLock and synchronized are both reentrant locks. The biggest function of reentrant locks is to avoid deadlocks.

class MyClass {    public synchronized void method1() {        method2();    }    public synchronized void method2() {    }}

After a thread acquires the lock of the method1 method, it enters method1 and the method2 method modified by synchronized does not need to obtain the lock again. Because synchronized is reentrant, it runs directly.

2. locks that can be interrupted

Locks: locks that can be interrupted.

In Java, synchronized is not an interruption Lock, but Lock is an interruption Lock, which is also an important reason for us to implement synchronization.

If thread A is executing the lock code and thread B is waiting to obtain the lock, thread B may not want to wait because the wait time is too long and want to handle other tasks first, we can let it interrupt itself or interrupt it in other threads. This is the locks that can be interrupted.

The lockInterruptibly () method of the previous Lock interface has reflected the tolerance of the Lock.

3. Fair lock

Fair locks are obtained in the order of request locks. For example, multiple threads are waiting for a lock. When the lock is released, the thread with the longest wait time (the thread with the first request) will obtain the lock. This is the fair lock.

Unfair locks cannot guarantee that the locks are obtained in the order of request locks. This may cause some or some threads to never get the lock.

In Java, synchronized is an unfair lock, which cannot guarantee the order in which the waiting thread obtains the lock.

For ReentrantLock and ReentrantReadWriteLock, it is a non-fair lock by default, but it can be set to a fair lock, which will be introduced later.

Implementation of Lock 1. ReentrantLock

ReentrantLock, that is, reentrant lock. The concept of reentrant lock has been introduced earlier. Now we can analyze and analyze ReentrantLock from the source code level. This will help us better understand Thread Scheduling and lock implementation, and master more ideas about high concurrency programming.

Implementation of the lock () method in ReentrantLock:

public void lock() {        sync.lock();    }

Sync statement:

/** Synchronizer providing all implementation mechanics */    private final Sync sync;

This part looks simple and understandable, but what puzzles you is sync and how it is implemented internally? Next let's take a look at how sync is implemented. By entering the lock source code, we found that:

Authorization + authorization + DQo8cD7K18/IztLDx8C0v7S/authorization/cG0se22vMrH08nL/MC0yrXP1rXEoaM8L3A + DQo8cHJlIGNsYXNzPQ = "brush: java;">/*** Head of the wait queue, lazily initialized. initialization t for * initialization, it is modified only via method setHead. note: * If head exists, its waitStatus is guaranteed not to be * CANCELLED. */private transient volatile Node head;/*** Tail of the wait queue, lazily initialized. modified only via * method enq to add new wait node. */private transient volatile Node tail;/*** The synchronization state. */private volatile int state; // records the number of times the current lock is locked

When the state value is 0, it indicates that it is not bound, and the lock is implemented through a higher state value. The change state is mainly implemented by the compareAndSetState function.The cas primitive is called to ensure the atomicity of the operation. If the state value is reset CT, it is updated to the update value and true is returned. Otherwise, the state is not changed and false is returned.

 /**     * Atomically sets synchronization state to the given updated     * value if the current state value equals the expected value.     * This operation has memory semantics of a {@code volatile} read     * and write.     *     * @param expect the expected value     * @param update the new value     * @return {@code true} if successful. False return indicates that the actual     *         value was not equal to the expected value.     */    protected final boolean compareAndSetState(int expect, int update) {        // See below for intrinsics setup to support this        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);    }

The current thread is in acactownablesynchronizer:

/**     * The current owner of exclusive mode synchronization.     */    private transient Thread exclusiveOwnerThread;

After learning about these basic data structures, let's look at sync. lock (), NonfairSync source code:

/*** Sync object for non-fair locks */static final class NonfairSync extends Sync {private static final long serialVersionUID = 73161535637828220.1l;/*** Performs lock. try immediate barge, backing up to normal * acquire on failure. */final void lock () {// If the lock is not locked by any thread and the lock is successful, set the current thread to the lock owner. // If the lock has been locked by the current thread, in acquire, 1 is added and if (compareAndSetState (0, 1) setExclusiveOwnerThread (Thread. currentThread (); else // lock failed. Try to lock again. If the lock fails, join the waiting queue and disable the current thread, acquire (1);} protected final boolean tryAcquire (int acquires) {return nonfairTryAcquire (acquires );}}

Implementation of acquire in AbstractQueuedSynchronizer:

/*** Acquires in exclusive mode, ignoring interrupts. implemented * by invoking at least once {@ link # tryAcquire}, * returning on success. otherwise the thread is queued, possibly * repeatedly blocking and unblocking, invoking {@ link * # tryAcquire} until success. this method can be used * to implement method {@ link Lock # lock }. ** @ param arg the acquire argument. this value is conveyed to * {@ link # TryAcquire} but is otherwise uninterpreted and * can represent anything you like. */public final void acquire (int arg) {// first try to get the lock, if the thread succeeds, return // otherwise, add the current thread to the lock wait queue and disable the current thread. // if (! TryAcquire (arg) & acquireQueued (addWaiter (Node. EXCLUSIVE), arg) selfInterrupt ();}

Try to obtain the lock and call nonfairTryAcquire (acquires); the implementation of this method is as follows:

/*** Performs non-fair tryLock. tryAcquire is implemented in * subclasses, but both need nonfair try for trylock method. */final boolean nonfairTryAcquire (int acquires) {final Thread current = Thread. currentThread (); int c = getState (); if (c = 0) {// if the lock is idle, try to lock it, if the thread is set as the lock owner if (compareAndSetState (0, acquires) {setExclusiveOwnerThread (current); return true ;}} // if the current thread is the lock owner, modify the lock status count else if (current = getExclusiveOwnerThread () {int nextc = c + acquires; if (nextc <0) // overflow throw new Error ("Maximum lock count exceeded"); setState (nextc); return true ;}// An Error occurred while trying to obtain the lock; false return false ;}

After tryAcquire fails, perform the following operations:

The first step is to call AbstractQueuedSynchronizer. addWaiter to add the current thread to the end of the waiting queue.

/**     * Creates and enqueues node for given thread and mode.     *     * @param current the thread     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared     * @return the new node     */    private Node addWaiter(Node mode) {        Node node = new Node(Thread.currentThread(), mode);        // Try the fast path of enq; backup to full enq on failure        Node pred = tail;        if (pred != null) {            node.prev = pred;            if (compareAndSetTail(pred, node)) {                pred.next = node;                return node;            }        }        enq(node);        return node;    }

The second step is to call AbstractQueuedSynchronizer. acquireQueued to enable the thread to go to the disabled state and attempt to obtain the lock each time it is awakened. If it fails, the thread will continue to be disabled.

/*** Acquires in exclusive uninterruptible mode for thread already in * queue. used by condition wait methods as well as acquire. ** @ param node the node * @ param arg the acquire argument * @ return {@ code true} if interrupted while waiting */final boolean acquireQueued (final Node node, int arg) {try {boolean interrupted = false; (;;) {// if the current thread is a direct successor to the head, try to get the lock // here it will not compete with other threads in the waiting queue, but it will The threads that have not entered the waiting queue compete for the lock. This is an important difference between unfair locks and fair locks. Final Node p = node. predecessor (); if (p = head & tryAcquire (arg) {setHead (node); p. next = null; // help GC return interrupted;} // if the head is not directly followed or the lock fails to be obtained, check whether the current thread needs to be disabled. // If yes, disable the lock, until it is locked. release wakeup or thread interruption if (shouldParkAfterFailedAcquire (p, node) & parkAndCheckInterrupt () interrupted = true ;}} catch (RuntimeException ex) {cancelAcquire (node ); throw ex ;}}

AbstractQueuedSynchronizer. shouldParkAfterFailedAcquire does a very important thing: clears the waiting queue according to the status and sets the waiting signal.

Here, we need to first describe waitStatus, which is a member variable of the static internal class Node of AbstractQueuedSynchronizer, used to record the thread Wait Status of the Node. the waiting status is 0 when the queue is started. If the waiting status is canceled, it is set to Node. CANCELLED. If the thread needs to wake up when the lock is released, other threads in the waiting queue will be set to Node. SIGNAL, there is also a status Node. CONDITION is not discussed here.

/** waitStatus value to indicate thread has cancelled */        static final int CANCELLED =  1;        /** waitStatus value to indicate successor's thread needs unparking */        static final int SIGNAL    = -1;        /** waitStatus value to indicate thread is waiting on condition */        static final int CONDITION = -2;

AbstractQueuedSynchronizer. shouldParkAfterFailedAcquire is implemented as follows:

/*** Checks and updates status for a node that failed to acquire. * Returns true if thread shoshould block. this is the main signal * control in all acquire loops. requires that pred = node. prev ** @ param pred node's predecessor holding status * @ param node the node * @ return {@ code true} if thread shocould block */private static boolean shouldParkAfterFailedAcquire (Node pred, node node) {int s = p Red. waitStatus; if (s <0)/** This node has already set status asking a release * to signal it, so it can safely park * // if the front node waitStatus has been set to SIGNAL, true is returned, and the thread return true can be disabled; if (s> 0) {// If the prefix result has been CALCEL, it is removed. /** Predecessor was canceled. skip over predecessors and * indicate retry. */do {node. prev = pred. prev;} while (pred. waitStatus> 0); pred. next = node;} else/** Indicate that we need a signal, but don't park yet. caller * will need to retry to make sure it cannot acquire before * parking. * // set the waitStatus of the front Node to SIGNAL compareAndSetWaitStatus (pred, 0, Node. SIGNAL); // here, false must be returned. It is possible that the front node has released the lock, however, because its waitStatus is not set to SIGNAL when the lock is released, and the wake-up Wait thread operation is not triggered, return false must be used to obtain the lock again; return false ;}

AbstractQueuedSynchronizer. parkAndCheckInterrupt is implemented as follows. It is very easy to directly disable the thread and wait for it to be awakened or interrupted. I have done some homework on Thread. interrupted () in java.

Here the thread is blocked. When waking up, the lock will be obtained again. If it fails, the thread will continue to be blocked. Even Thread. interrupted () cannot be interrupted. The threads that want to interrupt and exit after waiting for a long time can call ReentrantLoc. lockInterruptibly ().

/**     * Convenience method to park and then check if interrupted     *     * @return {@code true} if interrupted     */    private final boolean parkAndCheckInterrupt() {        LockSupport.park(this);        return Thread.interrupted();    }
ReadWriteLock

Java. util. concurrent. locks. readWriteLock is an interface class. We know the concept of read/write locks. It allows multiple threads to read specific resources at the same time, but only one thread can write resources.

Its Interface Definition:

public interface ReadWriteLock {    /**     * Returns the lock used for reading.     *     * @return the lock used for reading     */    Lock readLock();    /**     * Returns the lock used for writing.     *     * @return the lock used for writing     */    Lock writeLock();}

Read lock:When no write operation thread locks ReadWriteLock and no thread requires a write operation, multiple read threads are allowed to lock.

Write lock:When no threads read or write operations are performed, the write operation can be locked by a unique thread.

2. ReentrantReadWriteLock

After learning about the ReadWriteLock concept, we need to study its implementation ReentrantReadWriteLock. The concept and implementation of reentrant are described in detail above. Here we mainly introduce the implementation of ReentrantReadWriteLock's two main internal classes: readLock () and writeLock.

ReadLock internal implementation:

    public static class ReadLock implements Lock, java.io.Serializable {        private static final long serialVersionUID = -5992448646407690164L;        private final Sync sync;        protected ReadLock(ReentrantReadWriteLock lock) {            sync = lock.sync;        }        public void lock() {            sync.acquireShared(1);        }        public void lockInterruptibly() throws InterruptedException {            sync.acquireSharedInterruptibly(1);        }        public boolean tryLock() {            return sync.tryReadLock();        }        public boolean tryLock(long timeout, TimeUnit unit)                throws InterruptedException {            return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));        }        public void unlock() {            sync.releaseShared(1);        }        public Condition newCondition() {            throw new UnsupportedOperationException();        }        public String toString() {            int r = sync.getReadLockCount();            return super.toString() +                "[Read locks = " + r + "]";        }    }

WriteLock implementation:

    public static class WriteLock implements Lock, java.io.Serializable {        private static final long serialVersionUID = -4992448646407690164L;        private final Sync sync;        protected WriteLock(ReentrantReadWriteLock lock) {            sync = lock.sync;    }        public void lock() {            sync.acquire(1);        }        public void lockInterruptibly() throws InterruptedException {            sync.acquireInterruptibly(1);        }        public boolean tryLock( ) {            return sync.tryWriteLock();        }        public boolean tryLock(long timeout, TimeUnit unit)                throws InterruptedException {            return sync.tryAcquireNanos(1, unit.toNanos(timeout));        }        public void unlock() {            sync.release(1);        }        public Condition newCondition() {            return sync.newCondition();        }        public String toString() {            Thread o = sync.getOwner();            return super.toString() + ((o == null) ?                                       "[Unlocked]" :                                       "[Locked by thread " + o.getName() + "]");        }        public boolean isHeldByCurrentThread() {            return sync.isHeldExclusively();        }        public int getHoldCount() {            return sync.getWriteHoldCount();        }    }

From the declaration of class methods, it is difficult to see the difference between read and write locks. Here we analyze their direct differences.

(1) trylock () Get the lock

Source code implementation of tryReadLock:

/*** Performs tryLock for read, enabling barging in both modes. * This is identical in effect to tryAcquireShared blocks t for * lack of callto readerShouldBlock. */final boolean tryReadLock () {Thread current = Thread. currentThread (); for (;) {int c = getState (); // if a write lock occupies and the current thread is not setExclusiveOwnerThread, false if (exclusiveCount (c) is returned )! = 0 & getExclusiveOwnerThread ()! = Current) return false; int r = sharedCount (c); // obtain the number of shared locks if (r = MAX_COUNT) throw new Error ("Maximum lock count exceeded "); if (compareAndSetState (c, c + SHARED_UNIT) {if (r = 0) {firstReader = current; firstReaderHoldCount = 1;} else if (firstReader = current) {firstReaderHoldCount ++;} else {HoldCounter rh = cachedHoldCounter; if (rh = null | rh. tid! = GetThreadId (current) cachedHoldCounter = rh = readHolds. get (); else if (rh. count = 0) readHolds. set (rh); rh. count ++;} return true ;}}}

Source code implementation of tryWriteLock:

/*** Performs tryLock for write, enabling barging in both modes. * This is identical in effect to tryAcquire should t for lack * of callto writerShouldBlock. */final boolean tryWriteLock () {Thread current = Thread. currentThread (); int c = getState (); if (c! = 0) {int w = exclusiveCount (c); // if the exclusive holding tree is 0, or the current thread does not obtain setExclusiveOwnerThread, false if (w = 0 | current! = GetExclusiveOwnerThread () return false; if (w = MAX_COUNT) // The Maximum number of write threads exceeds throw new Error ("Maximum lock count exceeded");} if (! CompareAndSetState (c, c + 1) return false; setExclusiveOwnerThread (current); return true ;}

From the source code, we can see that ReadLock acquires the shared lock and WriteLock acquires the exclusive lock. The implementation of WriteLock is almost the same as that in ReentrantLock, and AQS acquire/release operations are used.

The state field (int type, 32-bit) is used to describe the number of threads holding the lock. In the era of exclusive locks, the value is usually 0 or 1 (if it is the number of retries), and in the era of shared locks, it is the number of locks held.

However, two variables are required to describe the number of read/write locks. In ReentrantReadWrilteLock, this field is split into two parts. A 16-bit value indicates the number of shared locks, the low 16 bits indicate the number of exclusive locks (or the number of retries ). 2 ^ 16-1 = 65536, which is why the maximum number of shared and exclusive locks mentioned in the previous section can only be 65535.

/*         * Read vs write count extraction constants and functions.         * Lock state is logically divided into two unsigned shorts:         * The lower one representing the exclusive (writer) lock hold count,         * and the upper the shared (reader) hold count.         */        static final int SHARED_SHIFT   = 16;        static final int SHARED_UNIT    = (1 << SHARED_SHIFT);        static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;        static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;        /** Returns the number of shared holds represented in count  */        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }        /** Returns the number of exclusive holds represented in count  */        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

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.