假如让我设计一把锁-ReentrantLock源码解析

生活中常见的锁--取款机(独占锁)

假设有一个取款机,所有的取钱的人都要进去取款,因此取款机相当于是被锁的代码块,一次只能有一个人进去,进去之后会将门上一个锁。门拥有的锁的一个数量 记为state。门打开是state为0,关闭的state是1


001.png

当A想要进去之后 ,他会对比门锁的状态是否和他期望的状态一致(期望state为0) 如果与他预期的一致,他将会进去并且把门上锁(state由0->1)。在上锁的时候我们必须保证是原子性的。因此用到CAS。

002.png

这个时候又有一个人B想要进去,但是他发现门上锁的数量与他期望的不一致(期望是0但是实际是1),这个时候他假设A可能会很快出来,因此他便在门口徘徊 (自旋),但是徘徊了很多次之后A仍然没有出来。

003.png

这个时候又来了很多很多人,他们都想去取钱,但是都获取不到锁,因此都在门口徘徊,人多时就会造成拥堵了交通之类的(线程过多会导致cpu的压力增大,甚至崩溃), 这个时候我们可以用到银行排号的方式,让所有人停止徘徊(线程被阻塞),都来拿一个号坐在那里等(等待队列)。

锁的基石

通过上面的讲解,我们大概了解实现一个锁需要什么了
1,记录锁的变量:state
2,获取锁的原子操作:CAS
3,自旋
4,阻塞和唤醒线程:SupportLock
5,存储线程的队列:双向链表

通过上面的一个流程我们大致可以写出以下的简易代码

public class AQS {
    private volatile int state = 0;
    private volatile BlockingQueue<Thread> threads = new ArrayBlockingQueue<Thread>(10);
    
    public void lock() {
        Thread current = Thread.currentThread();
        for(;;) {
            //CAS保证整个操作的原子性
            if(compareAndSetState(0, 1)) {
                //假设进入的线程获取了锁  , 跳出自旋的循环
                break;
            }
            //假设线程没有获取锁,释放线程资源
            LockSupport.park();
            //将释放了的线程放入队列里面  , 对所有的线程做一个记录,以防将来唤醒线程时找不到
            threads.add(current);
            
        }
    }
    public void release() {
        if(compareAndSetState(1, 0)) {
            //释放锁成功之后, 唤醒队列的第一个线程
            LockSupport.unpark(threads.poll());
        }
    }
     protected final boolean compareAndSetState(int expect, int update) {
            // See below for intrinsics setup to support this
           // return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
         return  true;
        }
}

通过以上代码,我们对代码流程有了一个基础的认识,但是就上面的代码是远远不够的 ,因此接下来我们来结合ReentrantLock看一看源码

ReentrantLock源码

为了让我们对执行流程有一个更清晰的认识,因此先上流程图


004.jpg

在ReentrantLock 里面有三个内部类
1,Sync 继承了 AbstractQueuedSynchronizer (大部分锁的基石),
2,NonfairSync 以及 fairSync继承了Sync。
本篇文章主要讲非公平锁(NonfairSync ),
在ReentrantLock 的注释上面,我们可以看到 使用范式如下所示。但是为什么lock.lock() 不能放在try里面呢?其实主要是为了防止释放的别的线程获取的锁

 * class X {
 *   private final ReentrantLock lock = new ReentrantLock();
 *   // ...
 *
 *   public void m() {
 *     lock.lock();  // block until condition holds
 *     try {
 *       // ... method body
 *     } finally {
 *       lock.unlock()
 *     }
 *   }

获取锁

 public ReentrantLock() {
        // 默认实现的是非公平锁
        sync = new NonfairSync();
}
public void lock() {
        sync.lock();
    }
  final void lock() {
            if (compareAndSetState(0, 1))
                // 获取到锁,并记录当前拥有的锁的线程
                setExclusiveOwnerThread(Thread.currentThread());
            else
                // 未获取到锁,接下来会通过自旋的方式尝试获取,
                acquire(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) {
            final Thread current = Thread.currentThread();
            int c = getState();
            // 如果此时恰好拥有锁的线程释放了,可以直接获取锁。
// 公平锁与非公平锁的区别就在这儿,公平锁在检测到锁被释放了之后,
//仍然需要到队列排队,而非公平锁无需排队,直接抢锁的使用权
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
//ReentrantLock是一个可重入的锁,如果当前线程与获取锁的线程是同一个,那么可以可重入的获取锁
            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;
        }
//如果再次获取锁失败,则将线程加入队列里面
    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;
    }
  
  private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

通过上面的代码 ,初始未获取锁的线程也可以得到两种结果,
1,重试获取锁成功
2,已经插入队列。
对插入队列里面的线程,我们可以通过自旋的方式再次让它尝试去获取锁,如果获取不到就阻塞当前线程。

    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) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
//一直没有获取到锁,取消线程获取锁的尝试,
                cancelAcquire(node);
        }
    }
//阻塞线程
   private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

到这里,获取锁的代码就讲完了,接下来看释放锁.

释放锁

释放锁的过程比较简单,通过改变 state状态,以及拥有锁的线程实现

 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;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

参考文档: https://juejin.im/post/5bfcf9896fb9a049b7800e20

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。