JUC锁相关

154 阅读4分钟

如何实现一把锁:

  1. 如何表示锁的状态:有锁、无锁
    boolean state;// true 有锁 false 无锁 只能表示两种状态
    为了实现锁重入,那么我们需要记录锁重入次数
    int times;
    两个变量有点冗余了,所以我们直接用 int state;来表示锁状态和重入次数:
    0无锁,大于0重入次数 特别地:1为重入一次,也即只加锁一次

  2. 如何保证多线程抢锁线程安全? CAS

  3. 如何处理获取不到锁的线程?
    自旋 阻塞.自旋+阻塞

  4. 如何释放锁? 自旋:自己抢锁
    阻塞:唤醒

image.png 自旋锁缺点:CPU 占用不干事,导致性能障碍,简称占着茅坑不拉屎 自旋锁优点:适用于执行步骤较少且快的操作,自旋一会儿马上就能获取到锁,这样不会消耗太多CPU资源 便用场景:争用较少且代码量小的 临界区

什么是AQS

image.png

AQS核心源码

//获取锁代码
public final void acquire(int arg) {
    if (!tryAcquire(arg) && //子类判定获取锁失败
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//获取锁失败后添加到阻塞队列
        selfInterrupt();
}

//子类实现获取锁逻辑,AQS不知道你怎么用state来上锁
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}
//释放锁代码
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 boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

一句话总结:
子类只需要实现自己的获取锁逻辑和释放锁逻辑即可,至于排队阻塞等待、唤醒机制均由AQS来完成。

ReentrantLock 原理

概念

基于AQS实现的可重入锁

核心变量和构造器
public class ReentrantLock implements Lock, java.io.Serializable {
    private final Sync sync;
    //默认非公平锁
    public ReentrantLock() {
        sync = new NonfairSync();
    }

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

image.png

image.png

abstract static class Sync extends AbstractQueuedSynchronizer {
    abstract void lock();

    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        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;
    }
    //公平锁和非公平锁的公用方法,因为在释放锁的时候不区分公平锁和非公平锁
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
        // 当前线程不是上锁的那个线程
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        if (c == 0) { //不是重入锁,那么当前线程一定是释放锁了,然后我们把当前AQS用于保存当前锁对象的变量ExclusiveOwnerThread设置为空,表明释放锁成功
            free = true;
            setExclusiveOwnerThread(null);
        }
        //注意:此时state全局变量没有改变,也就意味着在setState之前没有别的线程能够获取锁,保证了以上操作的原子性
        setState(c);
        //如果返回true,就是告诉AQS我当前释放锁成功了,可以去唤醒正在等待锁的线程了
        return free;
    }

    final ConditionObject newCondition() {
        return new ConditionObject();
    }
}

static final class NonfairSync extends Sync {
    //由ReentrantLock调用获取锁
    final void lock() {
        //非公平锁,直接抢锁,不管有没有线程排队
        if (compareAndSetState(0, 1))
            //上锁成功,标识当前线程为获取锁的线程
            setExclusiveOwnerThread(Thread.currentThread());
        else
            //抢锁失败,进去AQS标准的获取锁流程
            acquire(1);
    }

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


static final class FairSync extends Sync {
    //由ReentrantLock调用获取锁
    final void lock() {
        //直接进入AQS标准的获取锁流程
        acquire(1);
    }
    //AQS调用,子类自己实现获取锁的流程
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        //此时正好持有锁的线程释放了锁
        if (c == 0) {
            if (!hasQueuedPredecessors() &&  //这里和非公平锁的区别在于,hasQueuedPredecessors会看看队列中是否有线程在排队,没有的话在通过CAS抢锁
                compareAndSetState(0, acquires)) {
                //抢锁成功
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //当前线程就是获取锁的线程,那么这里就是锁重入,和非公平锁流程一样
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        //返回false表示需要AQS来将当前线程放入阻塞队列,然后进行阻塞操作等待唤醒获取锁
        return false;
    }
}