并发编程(8)AQS和ReentrantLock

什么是AQS

AQS,AbstractQueuedSynchronizer,抽象队列同步器。它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。

AQS的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态。

在这里插入图片描述

ReentrantLock

加锁逻辑

默认构造方法,创建一个NonfairSync,此类继承于AbstractQueuedSynchronizer。

public ReentrantLock() {
    sync = new NonfairSync();
}
final void lock() {
    //判断是否已经有人加锁
    if (compareAndSetState(0, 1))
        //设置当前线程是加了独占锁的线程,标识出来自己是加锁的线程
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

AQS里有一个核心的变量state,代表了锁的状态。如果state=0,代表没人加过锁,此时可以加锁,把这个state设置为1。

可重入锁逻辑:

假设线程1再次lock(),此时state=1,所以走acquire(1)这个方法。

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
    //获取当前线程->线程1
    final Thread current = Thread.currentThread();
    int c = getState();
    //再次判断是否有人加过锁,防止锁已经被释放
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //如果加锁线程一致,此时他们都是线程1
    else if (current == getExclusiveOwnerThread()) {
        //nextc=1+1=2
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        //state=2
        setState(nextc);
        return true;
    }
    return false;
}

线程2进行加锁:

public final void acquire(int arg) {
    //因为线程1占用着锁,因为是不同的线程,执行tryAcquire(arg)返回false。
    if (!tryAcquire(arg) &&
        //addWaiter(Node.EXCLUSIVE), arg)->线程2代表的Node
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
private Node addWaiter(Node mode) {
    //将当前线程(线程2)封装成了一个Node。处于阻塞等待状态的线程可以封装为一个Node双向链表(队列)。
    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;
}
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            //判断head是否为null,如果是null的话,就将head设置为空Node节点。
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            //比较tail变量是否为t,如果为t的话,那么tail指针就指向node。
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            //获取到node的上一个节点,即prev指针指向的节点
            final Node p = node.predecessor();
            //线程2尝试再次加锁,如果加锁成功,会将线程2对应的Node从队列中移除。
            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);
    }
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //默认情况下,watiStatus=0。
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        return true;
    if (ws > 0) {
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        // 将空Node的waitStatus设置为SIGNAL
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
private final boolean parkAndCheckInterrupt() {
    //将一个线程进行挂起,必须有另外一个线程来对当前线程执行unpark操作,才能唤醒挂起的线程。
    LockSupport.park(this);
    return Thread.interrupted();
}

公平锁:

ReentrantLock默认是非公平锁,当传入参数为true时,为公平锁。

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

逻辑与非公平锁基本上一致,唯一的不同在于调用tryAcquire()时增加了hasQueuedPredecessors的逻辑判断。

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    return false;
}
public final boolean hasQueuedPredecessors() {
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

公平锁,任何一个线程过来会先判断一下,当前是否有人在排队,而且是不是自己在排队,如果不是的话,说明有别人在排队,此时自己不能尝试加锁,直接入队阻塞等待,先来后到的顺序,后来的人一定是排到队列里去等待。

lock()流程图:
在这里插入图片描述

释放锁逻辑

public void unlock() {
    sync.release(1);
}
public final boolean release(int arg) {
    //当前线程尝试释放锁
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            //唤醒队列中的元素
            unparkSuccessor(h);
        return true;
    }
    return false;
}
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    //当前线程不等于加锁的线程,说明不是你加的锁
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    //设置state=0
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}
private void unparkSuccessor(Node node) {

    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    
    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;
    }
    //后继节点不为null时唤醒该线程
    if (s != null)
        LockSupport.unpark(s.thread);
}

unlock()流程图:
在这里插入图片描述

锁优化策略

1、如果一些线程只会写一个变量,另外一个线程是来读取这个变量的值,那么此时优先使用volatile。比如通过修改volatile标志位实现服务优雅停机机制。

2、如果线程都要写,并且只是简单的数值累加或者变更的操作,此时可以用 Atomic原子类。比如微服务注册中心项目中的服务心跳计数器。

3、多线程并发访问一块共享数据, 读多写少的场景,建议使用读写锁。比如微服务注册中心项目中每隔30秒增量拉取注册表。

4、尽量保证加锁的时间是很短的,不要在加锁之后,执行一些磁盘文件读写、网络IO读写,导致锁占用的时间过于长。

5、分段加锁,实践,尽可能的减少一个线程占用锁的时间。

6、尽可能对不同功能分离锁的使用,在使用不同的功能的时候,可以用不同的锁,这样降低线程竞争锁的冲突。比如阻塞队列就使用了两把锁,队头是一把锁,队尾是一把锁,你从队列尾巴插进去是加的一把锁,从队头消费数据使用的是另外一把锁,入队和出队的操作,就不会因为锁产生冲突。

7、避免在循环中频繁的加锁以及释放锁。

8、尽量减少高并发场景中线程对锁的争用,比如用多级缓存机制。

猜你喜欢

转载自blog.csdn.net/qq40988670/article/details/86613372