[Logic of Java Programming] Atomic Variables & CAS & Display Lock

atomic variable

In the understanding of synchronized, synchronized is used to ensure atomic update operations, but the cost of using synchronized is too high. You need to acquire the lock first, and then release the lock at the end. If you can't get the lock, you need to wait. These costs are relatively high, and for this case, atomic variables can be used.
The basic atomic variable types in Java concurrent packages are as follows:

  • AtomicBoolean: Atomic Boolean type, often used to represent a flag in a program
  • AtomicInteger: Atomic Integer type
  • AtomicLong: Atomic Long type, often used to generate unique serial numbers in programs
  • AtomicReference: Atomic reference type used to atomically update complex types

AtomicInteger

introduce

Construction method

// 给定一个初始值
public AtomicInteger(int initialValue) 
// 初始值为0
pubilc AtomicInteger()

It contains some methods that implement composition operations atomically

// 以原子方式获取旧值并设置新值
public final int getAndSet(int newValue);
// 以原子方式获取旧值并给当前值加1 
public final int getAndIncrement();
// 以原子方式获取旧值并给当前值减1 
public final int getAndDecrement();
// 以原子方式获取旧值并给当前值加delta
public final int getAndAdd(int delta); 
// 以原子方式给当前值加1并获取新值
public final int incrementAndGet();
// 以原子方式给当前值减1并获取新值
public final int decrementAndGet();

The implementation of these methods all depend on another public method

public final boolean compareAndSet(int expect, int update);

compareAndSet method, referred to as CAS . This method has two parameters expect and udpate. If the current value is equal to expect, it will be updated to update, otherwise it will not be updated. If the update is successful, it will return true, otherwise it will return false. This operation is atomic.

AtomicInteger is simple to use

public class AtomicIntegerDemo {
    private static AtomicInteger counter = new AtomicInteger();
    static class Visitor extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                counter.incrementAndGet();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[100];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Visitor();
            threads[i].start();
            threads[i].join();
        }
        System.out.println(counter.get());
    }
}

The output of the program is always correct as 100000

AtomicInteger Fundamentals

Most of the method implementations of AtomicInteger are similar, let's look at one method:

public final int incrementAndGet() {
    for(;;) {
        int current = get();
        int next = current + 1;
        if(compareAndSet(current, next)) {
            return next;
        }
    }
}

The code is an infinite loop, first obtain the current value current, calculate the expected value next, and then call the CAS method to update, if the update is unsuccessful, indicating that the value has been changed by another thread, then go to get the latest value and try to update until it succeeds

How is compareAndSet implemented?

public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

The definition of unsafe is private static final Unsafe unsafe = Unsafe.getUnsafe();located in the sun.misc package.
In principle, general computer systems directly support CAS instructions at the hardware level

Comparison with synchronized

Compared to synchronized locks, this way of atomic updates represents a different way of thinking.
Synchronized is pessimistic, it assumes that updates are likely to conflict, so the lock is acquired first, and then the update is performed.
Updates to atomic variables are optimistic, assuming fewer conflicts, but if they do, keep trying to update.

Synchronized is a blocking algorithm. When the lock is not obtained, it enters the waiting queue and waits for other threads to wake up, which has the overhead of context switching.
Updates to atomic variables are non-blocking.

implement lock

Based on CAS, pessimistic blocking algorithm can be implemented

public class MyLock {
    private AtomicInteger status = new AtomicInteger(0);
    public void lock() {
        while(!status.compareAndSet(0, 1)) {
            Thread.yield();
        }
    }
    public void unlock() {
        status.compareAndSet(1, 0);
    }
}

show lock

Interface Lock

public interface Lock {
    // 获取锁
    void lock();
    // 获取锁,可以响应中断
    void lockInterrupteibly() throws InterruptedException;
    // 尝试获取锁,立即返回,不阻塞
    boolean tryLock();
    // 先尝试获取锁,如果能成功则立即返回true,否则阻塞等待,但等待的最长时间由参数设置,在等待的同时可以响应中断
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    // 释放锁
    void unlock();
    Condition newCondition();
}

Compared with synchronized, display lock supports acquiring locks in a non-blocking manner, can respond to interrupts, and can be time-limited, making it much more flexible.

Reentrant Lock ReentrantLock

Basic usage

The main implementation class of the Lock interface is ReentrantLock

public ReentrantLock()  
public ReentrantLock(boolean fair)

The parameter fair indicates whether fairness is guaranteed. If not specified, the default value is false, indicating that fairness is not guaranteed. The so-called fairness means that the thread that waits the longest is limited to obtain the lock. Guaranteed fairness affects performance and is generally not required

Implementation principle

The implementation of ReentrantLock depends on the class LockSupport.
LockSupport is also located under java.util.concurrent.locks, and its main methods are as follows

// 使当前线程放弃CPU,进入WAITING状态
public static void park()
public static void parkNanos(long nanos)
public static void parkUntil(long deadline) 
public static void unpark(Thread thread)

park will cause the current thread to give up the CPU and wait for state i until another thread calls unpaark on it, and unpark restores the thread specified by the parameter to the runnable state.
park is different from Thread.yield(), yield just tells the operating system to let other threads run first, but it can still run, and park will give up the scheduling qualification and make the thread enter the WAITING state

AQS

ReentrantLock can be implemented using the basic methods provided by CAS and LockSupport.
Java provides an abstract class AbstractQueuedSynchronizer, referred to as AQS, to simplify the implementation of concurrent tools.
AQS encapsulates a state and provides subclasses with methods to query and set the state

private volatile int state
protected final int getState()
protected final void setState(int newState)
protected final boolean compareAndSetState(int expect, int update)

When used to implement locks, AQS can save the current thread holding the lock and provide methods for querying and setting

private transient Thread exclusiveOwnerThread;
protected final void setExclusiveOwnerThread(Thread t);
protected final Thread getExclusiveOwnerThread();

AQS maintains a waiting queue internally, and uses the CAS method to implement a non-blocking algorithm for updating

ReentrantLock

ReentrantLock uses AQS internally and has three inner classes

abstract static class Sync extends AbstractQueuedSynchronizer  
static final class NonfairSync extends Sync  
static final class FairSync extends Sync  

NonfairSync is the class used when fair is false, and FairSync is the class used when fair is true. There is a Sync variable in ReentrantLock, through which the specific invocation of the method comes

Let's look at the implementation of lock first

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

We use the default Nonfair class for analysis

final void lock() {
    // 如果当前未被锁定,则立即获得锁
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
    // 获得锁
        acquire(1);
}

ReentrantLock uses state to represent the state of the lock.
If it is not currently locked, the lock is obtained immediately, otherwise acquire(1)the lock is obtained.
acquire(1)is a method in AQS

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

To call tryAcquireto acquire the lock, tryAcquire must be overridden
by subclasses NonfairSync implemented as follows:

// NonfairSync
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

nonfairTryAcquire is implemented in sync:

// Sync
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    // 如果未被锁定,则使用CAS进行锁定
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 如果已经被当前线程锁定,则增加锁定次数
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

If it tryAcquirereturns false, AQS calls:

// addWaiter会新建一个节点,代表当前线程,然后加入内部的等待队列中
acquireQueued(addWaiter(Node.EXCLUSIVE), arg);

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) &&
                // 调用LockSupport.park放弃CPU,返回中断标志
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

Summarize the basic process of the lock method:
1. Obtain the lock immediately if it can obtain the lock, otherwise join the waiting queue
2. After being woken up, check whether it is the first waiting thread, and return if it is and can obtain the lock. Otherwise, continue to wait
3. During this process, if an interrupt occurs, the lock will record the interrupt flag, but will not return early or throw an exception

The implementation of unlock is mainly to modify the state to release the lock.
However, the difference between FairSync and NonfairSync is that when acquiring a lock, FairSync has an additional detection, and it will acquire the lock only if there are no other threads waiting for a longer time.

The overall performance of ensuring fairness is relatively low. The reason for the low level is not that there is an extra check here, but that active threads cannot get locks and enter a waiting state, causing frequent context switching and reducing overall efficiency.

display conditions

Locks are used to solve the problem of race conditions, the condition is the cooperation mechanism between threads.
wait/notify works with synchronized, and display conditions work with display locks.

Condition represents a condition variable and is an interface

public interface Condition { 
    void await() throws InterruptedException;
    void awaitUninterruptibly();
    long awaitNanos(long nanosTimeout) throws InterruptedException;
    boolean await(long time, TimeUnit unit) throws InterruptedException;
    boolean awaitUntil(Date deadline) throws InterruptedException;  
    void signal();
    void signalAll();
}

await corresponds to Object's wait, signal corresponds to notify, and signalAll corresponds to notifyAll
before calling the await method. You need to acquire the lock first. After entering the waiting queue, await will release the lock and release the CPU. When other threads wake it up, or after waiting for a timeout, or after an interrupt exception occurs, it needs to re-acquire the lock. After the lock is acquired, it will be released from the await method. quit

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325875238&siteId=291194637