Six, explicit locks and AQS

Explicit locks and AQS

An explicit lock

Synchronized keyword combination object's monitor, JVM provides a "built-Lock" semantics for us, this lock is very simple, we do not need to be concerned about locking and lock release process, we only need to tell the virtual machine which code blocks need to lock, other details will be realized by the compiler and virtual machine itself.

Our "built-Lock" can be understood as a built-in features of the JVM, so a very significant problem is that it does not support some advanced customization features, for example, I want to support fair competition in the lock, I want to thread is blocked according to different conditions in different queues, I want to support regular competition lock timeout to return, I also want blocked thread can interrupt requests, and so on.

These special needs are "built-Lock" can not be met, so the JDK levels and the introduction of the concept of "explicit lock" is no longer responsible for the JVM to lock and release the lock, the action program to be released to us do, the level of inevitable complexity of the program a bit, but increase the flexibility of the lock, can support more customization features, but require you to have a deeper understanding of the lock.

[1] explicitly lock Lock

Lock java.util.concurrent.locks interface is located under the package, substantially defined as follows:

public interface Lock {
    //获取锁,失败则阻塞
    void lock();
    //响应中断式获取锁
    void lockInterruptibly()
    //尝试一次获取锁,成功返回true,失败返回false,不会阻塞
    boolean tryLock();
    //定时尝试
    boolean tryLock(long time, TimeUnit unit)
    //释放锁
    void unlock();
    //创建一个条件队列
    Condition newCondition();
}

Explicit lock realize there are three main categories, ReentrantLock is its main implementation class, ReadLock and WriteLock is ReentrantReadWriteLock internally defined two inner classes, they inherit from Lock and implements all the methods of their definition, fine reader separation. The ReentrantReadWriteLock provide outward read locks to write locks.

[2] of ReentrantLock (reentrant lock)

ReentrantLock as an explicit lock Lock achieve the most basic, it is also the most frequently used a lock implementation class. Reentrant is to re-acquire the lock, such as recursion.

It provides two constructors to support fair competition lock.

public ReentrantLock()
//参数fair为是否支持公平竞争锁
public ReentrantLock(boolean fair)

The difference between the fair at the fair locks and non-lock that locks in fairness when selecting a thread owns the lock, refer to the principle of first come first served, the longer the waiting time for a thread will have a higher priority. Rather than fair lock then disregard this principle.

I assume such a situation, A to obtain the lock is running, B failed attempt to acquire the lock is blocked, then C is also trying to get a lock, blocking failure, although C requires only a very short running time, it still needs to wait for the end of the Executive B have a chance to acquire a lock to run.

Under the premise of non-arm lock, A execution ends, find the head of the queue thread B, initiate a context switch, if this time the lock C over the competition, under the premise of non-equity strategy, C is to acquire a lock, and assuming it quickly execution is over, when the thread B is switched back again to acquire the lock does not really matter, as a result, C thread performs a context switch at the end of the process B thread. Obviously, the throughput of the CPU under non-equity strategy is to improve.

However, non-lock fairness policy may cause some threads hunger, not always running, advantages and disadvantages, timely choice.

[3] ReadWriteLock (read-write lock)

① ReadWriteLock Like Lock is an interface that provides the operating mechanism readLock and writeLock other kinds of locks, one for read-only lock is a write lock.

Read lock may be held simultaneously by multiple threads at the time did not write lock, write lock is exclusive (exclusive). Only one write thread, but you can have multiple threads concurrently read data.

Achieve all read-write lock must ensure that the memory read write operations affect operations. In other words, get a read lock thread must be able to see the contents of a pre-release of the updated write lock.

In theory, read and write than the mutex lock allows concurrent for greater sharing of data. Compared with mutexes, read-write lock is able to improve the performance of reading and writing data depends on the frequency, the duration of the read write operation, and the competition between the read and write threads and threads.

② mutually exclusive principles:

  • Read - can coexist,
  • Read - write can not coexist,
  • Write - write can not coexist.

③ReentrantReadWriteLock

ReentrantReadWriteLock for the implementation class ReadWriteLock

The lock allows read and write thread to thread ReentrantLock syntax reacquire read-write lock. Until all write locks held by the writing thread is released, do not allow non-reentrant read threads.

In addition, the write lock (writer thread) can acquire a read lock, but does not allow read lock (read thread) acquire a write lock. In other applications, when a write lock remains during the process of executing the read at a read lock or a callback, reentrancy may be very useful.

Example code:

public class TestReadWriteLock {

    public static void main(String[] args){
        final ReadWriteLockDemo rwd = new ReadWriteLockDemo();
        //启动100个读线程
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    rwd.get();
                }
            }).start();
        }
        //写线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                rwd.set((int)(Math.random()*101));
            }
        },"Write").start();
    }
}

class ReadWriteLockDemo{
    //模拟共享资源--Number
    private int number = 0;
    // 实际实现类--ReentrantReadWriteLock,默认非公平模式
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    //读
    public void get(){
        //使用读锁
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+" : "+number);
        }finally {
            readWriteLock.readLock().unlock();
        }
    }
    //写
    public void set(int number){
        readWriteLock.writeLock().lock();
        try {
            this.number = number;
            System.out.println(Thread.currentThread().getName()+" : "+number);
        }finally {
            readWriteLock.writeLock().unlock();
        }
    }
}
/**
Thread-50 : 0
Thread-19 : 0
Thread-54 : 0
Thread-57 : 0
Thread-31 : 0
Write : 40
Thread-61 : 40
Thread-62 : 40
Thread-35 : 40
Thread-32 : 40
    
*/

First, start reading the thread, then number is 0; then at some point wrote threads modifies a shared resource number data, read thread reads the latest value again

Two, AQS-depth analysis

[1] What is the AQS

AQS AbustactQueuedSynchronizer is short, it is a Java class underlying synchronization tool provided by the synchronization state indicates a variable of type int (state), and provides a series of CAS operations to manage the synchronized state. AQS main role is to provide a unified underlying support for Java concurrency synchronization component, for example ReentrantLock , CountdowLatch is based AQS implementation, usage is through inheritance AQS achieve its template method, then the sub-class as an inner class synchronization components.

[2] the synchronous queue

Synchronization queue is a very important part of AQS, it is a double-ended queue, follow the FIFO principle, the main role is to kept in a locked blocked thread when a thread tries to acquire the lock, if already occupied, then the current thread will be constructed as a node node is added to the tail of the synchronization queue, the queue head node is successfully acquired lock node, the head node when a thread releases the lock, the node will wake behind the head and releasing the reference to the current node.

[3] state status

AbstractQueuedSynchronizer maintains a volatile int type of variable, the user indicates the current synchronization status. volatile not guarantee atomicity operations, but to ensure the visibility of the current state variable. As for the specific semantics of volatile, you can refer to my articles. state visit in three ways:

  • getState()
  • setState()
  • compareAndSetState()

These three are called the atomic operation, which depends on the implemented compareAndSetState Unsafe of compareAndSwapInt () method. Code is implemented as follows:

 /**
     * The synchronization state.
     */
    private volatile int state;
  
    /**
     * Returns the current value of synchronization state.
     * This operation has memory semantics of a {@code volatile} read.
     * @return current state value
     */
    protected final int getState() {
        return state;
    }

    /**
     * Sets the value of synchronization state.
     * This operation has memory semantics of a {@code volatile} write.
     * @param newState the new state value
     */
    protected final void setState(int newState) {
        state = newState;
    }

    /**
     * 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);
    }

[4] sharing resources

AQS defines two resource sharing: Exclusive (exclusive, only one thread can perform, such as of ReentrantLock) and Share (shared, multiple threads may be executed simultaneously, such as Semaphore / CountDownLatch).
  Different custom synchronizer contention for shared resources in different ways. Custom synchronizer when implementing only way to achieve access to shared resources and the release of state can be, but the specific thread waits to maintain the queue (access to resources such as failure of the team / wake-out teams), AQS has achieved good on the top floor. Custom mainly the following several ways to achieve the synchronizer is:

  • isHeldExclusively (): this thread is monopolizing resources. Only use condition only need to achieve it.
  • tryAcquire (int): exclusively. Try to acquire resources, returns true if successful, false if it fails.
  • tryRelease (int): exclusively. Attempt to free resources and returns true if successful, false if it fails.
  • tryAcquireShared (int): sharing. Try to acquire resources. A negative number indicates a failure; 0 indicates success, but there is no remaining available resources; positive number indicates success, and there are remaining resources.
  • tryReleaseShared (int): sharing. Attempt to free resources, if allowed to release the follow-up wait wake node returns true, otherwise false.

[5] acquire and release locks processes

The following exclusive lock on to explain the process to obtain and release locks in the AQS:

Obtain:

  1. Calling Custom synchronizer tryAcquire () attempts to direct resources to the acquisition, if successful direct return;
  2. Did not succeed, then addWaiter () the threads are waiting for the end of the queue, and marked as exclusive mode;
  3. acquireQueued () thread to rest in the waiting queue, when the opportunity arises (in their turn, will be unpark ()) will try to obtain resources. To obtain resources before returning. If the wait is interrupted throughout the process, it returns true, otherwise false.
  4. If the thread is interrupted while waiting, it is not responding. Just before re-access to resources for self-interrupt selfInterrupt (), will break up on.

Source Analysis:

acquire an acquisition exclusively resource, if the resource is acquired, directly back to the thread, otherwise enter the queue, until resources get so far, and the whole process ignore the impact of interruptions. This method is a shared resource exclusive mode thread gets top entrance. After obtaining the resources, you can go to the thread execute its critical section code. Here is the acquire () source code:

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

tryAcquire attempt to monopolize access to resources the way, is returned directly succeed if true, otherwise direct returns false. The method may be used to achieve tryLock () method of Lock. The default implementation of this method is to throw UnsupportedOperationExceptionspecific implementation implemented by a custom extension of AQS synchronization classes. AQS here is only responsible for the definition of a common methodological framework. The reason here is not defined as abstract, because the exclusive mode only achieve tryAcquire-tryRelease, the shared mode only achieve tryAcquireShared-tryReleaseShared. Are defined as If abstract, then each mode is also the interface to be fulfilled in another mode.

protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

  addWaiter The method for different modes according to the current thread ( Node.EXCLUSIVEexclusive mode, Node.SHAREDto a sharing mode) to the tail of the queue, and returns the current node thread located. If the queue is not empty, by the places compareAndSetTailin a manner CAS node is added to the current thread to wait for the end of the queue method. Otherwise, the initialization enq (node) method of a wait queue, and returns the current node. Source as follows:

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;
}

  acquireQueued queue for the thread to spin and exclusive non-interruptible way to obtain the synchronization state (acquire), until after the lock to get back. Implementation of the method is divided into two parts: If the current node has become the head node, attempts to acquire the lock (to tryAcquire) successfully, and then returns; otherwise, check whether the current node should be park, park and then the thread checks whether the current thread may be interrupted.

final boolean acquireQueued(final Node node, int arg) {
    //标记是否成功拿到资源,默认false
    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);
    }
}

freed:

  release method is a shared resource exclusive mode thread releases the top-level entrance. It will release a specified amount of resources, if completely released (ie state = 0), it will wake up other threads waiting in the queue to get the resources. This is exactly the unlock () semantics, of course not confined to unlock (). The following is a release () source code:

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

    /**
     * Attempts to set the state to reflect a release in exclusive
     * mode.
     *
     * <p>This method is always invoked by the thread performing release.
     *
     * <p>The default implementation throws
     * {@link UnsupportedOperationException}.
     *
     * @param arg the release argument. This value is always the one
     *        passed to a release method, or the current state value upon
     *        entry to a condition wait.  The value is otherwise
     *        uninterpreted and can represent anything you like.
     * @return {@code true} if this object is now in a fully released
     *         state, so that any waiting threads may attempt to acquire;
     *         and {@code false} otherwise.
     * @throws IllegalMonitorStateException if releasing would place this
     *         synchronizer in an illegal state. This exception must be
     *         thrown in a consistent fashion for synchronization to work
     *         correctly.
     * @throws UnsupportedOperationException if exclusive mode is not supported
     */
protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

/**
     * Wakes up node's successor, if one exists.
     *
     * @param node the node
     */
private void unparkSuccessor(Node node) {
    /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
    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);
}

Similar to tryAcquire acquire () method (), tryRelease () method also requires a custom synchronizer exclusive mode to achieve. Normally, tryRelease () will be successful, because it is the exclusive mode, the thread to release resources, then it must have been given exclusive resources, and direct the appropriate amount of resources lost to (state- = arg), also no need to consider thread safety issues. But pay attention to its return value, already mentioned above, release () is based on tryRelease () return value to determine whether the thread has completed freed up resources! So self-righteous set when implementing synchronizers, if you have to completely release resources (state = 0), to return true, otherwise false.
  unparkSuccessor(Node)A method for waking up a thread waiting for the next queue. It should be noted here that the next thread is not necessarily the next current node, but can be used to wake up the next thread, if the node exists, call the unpark()method wake.
  In short, release () is a shared resource exclusive mode thread releases the top-level entrance. It will release a specified amount of resources, if completely released (ie state = 0), it will wake up other threads waiting in the queue to get the resources.

Third, custom exclusive lock

public class Mutex implements Lock {
    // 静态内部类,自定义同步器
    private static class Sync extends AbstractQueuedSynchronizer {
        // 是否处于占用状态
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        // 当状态为0的时候获取锁
        public boolean tryAcquire(int acquires) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        // 释放锁,将状态设置为0
        protected boolean tryRelease(int releases) {
            if (getState() == 0) throw new
                    IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        // 返回一个Condition,每个condition都包含了一个condition队列
        Condition newCondition() {
            return new ConditionObject();
        }
    }

    // 仅需要将操作代理到Sync上即可
    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.tryAcquireNanos(1, unit.toNanos(timeout));
    }
}

Guess you like

Origin www.cnblogs.com/lee0527/p/11695893.html
AQS
AQS