Lock加锁过程源码解析

Lock源码解析第一篇

公平锁加锁过程

​ ReetrantLock 实现 Lock接口,在这个ReetrantLock类中维护了一个对象:

​ private final Sync sync;

​ 这个Sync extends AbstractQueuedSynchronized 这个AbstractQueuedSynchronized 就是我们平时说的AQS

​ AQS 里面有三个元素很重要: 同步队列

​ private transient volatile Node head; //队列头

​ private transient volatile Node tail; //队列尾

​ private volatile int state; //锁状态,加锁成功则为1, 重入为+1 解锁则为0

public class Node{

    volatile Node prev;
    volatile Node next;
    volatile Thread thread;
}

//Lock 接口,里面这么多方法的
public interface Lock {
    
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}
//先来写个小例子
//我们经常用的lock.lock

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class AQSDemo {

    public static void main(String[] args) {

        //lock.lock()  一定要写在try模块的外面
        //因为一旦写在里面,获取到了锁,一抛异常,走finally就是释放锁
        //finally  这个东西一定要写,保证锁 100% 释放
         
        //ReentrantLock 它实现的就是上面的Lock接口
        Lock lock = new ReentrantLock();
        lock.lock();
        try{
            Thread.sleep(10000);
        }catch (Exception e){
            lock.unlock();33
        }
    }
}

//我假设有三个线程: 1、2、3 来解释一下公平锁的流程,然后带着这个流程去看源码解析

//AQS其实就是维护了锁的资源,state
//第一个线程来了,可以直接占用这个资源,表名已经上锁成功
//第二个线程来了,第二个线程肯定是要竞争锁资源的,但是由于此时锁资源被第一个线程占用,一直在无限循环
//第三个线程来了,第三个线程就不会竞争资源了,为什么呢?第二个都没有获取到锁,第三个就没有资格去竞争
// Doug Lea在第三个线程这边做了一个很有意思的东西,信号量(不要把它当成线程中的那个信号量,不是同一个概念)
//shouldParkAfterFailedAcquire() 待着疑问去看这个方法
//ReentrantLock 实现了接口Lock
public class ReentrantLock implements Lock, java.io.Serializable {
   
   //这个里面有两种锁,  公平锁  非公平锁
    public void lock() {
        sync.lock();
    }
    
    //公平锁
    static final class FairSync extends Sync {
        
        final void lock() {
            acquire(1);
        }
    }
    
    //非公平锁
    static final class NonfairSync extends Sync {
        
        final void lock() {
            if (compareAndSetState(0, 1)){
                setExclusiveOwnerThread(Thread.currentThread());
            }else{
                acquire(1);
            }
        }
    }
    
}

此篇,我只说一下公平锁。 @author Doug Lea 写线程 优化线程的超级大佬,先膜拜一下

//才一开始这个方法的调用点儿, acquire(1);  arg == 1   1代表加锁成功
public final void acquire(int arg) {
    
    //if条件是:
    //先要尝试获取锁tryAcquire(arg), 如果获取锁失败了,就执行后面的acquireQueued(加入等待队列)
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

//公平锁

//讲述的是  尝试获取锁的,然后说明一下  acquireQueued(addWaiter(Node.EXCLUSIVE)
static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    final void lock() {
        acquire(1);
    }

    	/**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
    protected final boolean tryAcquire(int acquires) {
        //拿到当前线程
        final Thread current = Thread.currentThread();
        //获取一下加锁的状态 state 这个值是在AQS里面维护的
        //这个是获取当前锁资源的状态,看看是否已经上锁成功  1已经加锁 0还未加锁
        //第一个线程进来,这个c肯定是0,未加锁
        //那么,它就应该尝试去加锁
        
        //总结一句话:getState()这个获取的不是某个线程的状态,是AQS的状态,是资源的状态,
        //线程都是通过CAS来改变这个状态。 lock加锁 都是抢占这个AQS资源的
        int c = getState();
        if (c == 0) {
            //我们首先得判断,当前线程来加锁,是否有等待队列,为什么这么说?
            //谁知道当前线程是第一个线程还是第N个线程,资源只能由一个线程占有,
            //其它的线程怎么办?既然是公平锁,就排队吧,因此有了一个等待队列的概念
            //如果,没有等待队列,那么就直接用compareAndSetState(0, acquires) 修改状态
            //把c从0修改成1,证明当前线程加锁成功
            //这个等待队列怎么判断有没有正在等待的值?
            if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
                //表示加锁成功,独占资源了,独占锁
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        
        //重入锁,文章一开始我有说过重入锁 +1的概念
        //因此,Lock支持重入锁的
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
    
}

//这个方法就是上面判断是否有等待队列(等待加锁的线程)的方法
public final boolean hasQueuedPredecessors() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    
    Node t = tail;	// Read fields in reverse initialization order
    Node h = head;
    //一开始,我不是很理解Node s  这个s是什么东西?
    //我翻译了一下这个英文解释,最后一句话的理解 the current  thread is first in queue 
    //当前线程是队列中的第一个,我就是把这个s当成当前线程了 能解释的通
    Node s;	
    //h != t  队列头 不等于  队列尾  
    //一开始,h是等于t的 为什么?这个是个空队列啊,都是null
    // 所以一开始刚进来的时候,h != t 为false,不执行&&后面的代码了,直接返回false(我不需要排队了,直接去	  //			获取锁)
    
    //(s = h.next) == null  队尾的下一个元素为null
    // s.thread != Thread.currentThread()
    
    //(s = h.next) == null 判断空节点有没有下一个节点 == null 没有下一个节点
    return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
}

//acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//如果尝试加锁失败了,就要放入队列中去等待了,在这里我要详细说明这个过程

private Node addWaiter(Node mode) {
    //把线程放到队列里,不是放线程,而是把线程封装到Node里面
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    //当前队列尾巴节点不为null  证明已经有队列了,直接放到当前队列中tail的下一个位置就好了
    //一开始进来的时候,队列都没有,pred肯定是null的,不走这个if逻辑
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    //第一个线程进来放入到等待队列,尾结点肯定是null,不走上面的逻辑
    enq(node);
    return node;
}

// 还没有队列的时候,就要放入元素,怎么放入呢?  就是下面的这个逻辑
//先虚拟出来一个队列  然后把当前Node放入到虚拟队列后面  CAS转换一下位置
//就把当前Node放入到了队列中了,大神的这种思路很厉害的  CAS保证了原子性,不会出现脏数据

//这边有个问题就是:在虚拟队列的时候为什么要 new Node() 这么一个空节点
//这个会在解锁的时候用到,你就耐着性子往下看!!!!!

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        //这个时候t == null 证明还没有这个队列,所以我们先要虚拟出来一个队列
        //通俗点儿,就是,我们还没有new队列呢
        if (t == null) { // Must initialize
            //这里就是虚拟处理一个队列  new了一个队列
            //这里肯定就有疑问了?if了,else怎么走?
            //看看这个 for(;;) 这个东西类似于 while(true)相当于一个无限循环
            //当前线程封装的Node 第二次循环 肯定就会进 else了
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            //当前Node的前一个节点是初始化的new Node()  new Node()--Node(当前)
            node.prev = t;
            //compareAndSetTail 位置换一下  Node -- new Node()
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

//由上面知道,这块代码由于尝试加锁失败,并且放入到了等待队列中,才会走到这里的
//Doug Lea   这个逼很优秀的,
//进来线程,尝试加锁,加锁失败,放入到队列中,放入到队列后干什么呢?
//我就要判断我上一个节点是什么状态,如果上一个节点处于park状态,那么我也得park(等待),
//如果上一个节点不是park状态,就得看我在不在队列的头部了,判断我是不是在第二个(因为第一个永远是空)
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        //加锁,又是一个 while(true)
        for (;;) {
            //node.predecessor() 拿当前节点的上一个节点
            final Node p = node.predecessor();
            //如果上一个节点是头部 p == head
            //如果上一个节点不是头部 就是走下面的if逻辑了,头部都park了,我也得park 公平锁嘛
            //tryAcquire(arg) 又是同样的加锁的过程,不再写了,上面已经写过了
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            //这个if就是当前节点的上一个节点不是head,我就得park了   看后面的解释
            //这个是通过当前节点得到上一个节点设置为 可以获取锁的状态  并把自己睡眠了(不能获取锁资源的)
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

volatile int waitStatus;
static final int SIGNAL  = -1;

//这段代码写的就很精彩了,我得认真写一下
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    
    int ws = pred.waitStatus; //  默认是 0 
    
    //ws默认0
    //第二次缓存进来就返回这个true了
    //具体的,看这个代码块最下面的解释
    if (ws == Node.SIGNAL){
        return true;
    }
    
    if(ws > 0){
       do {
           node.prev = pred = pred.prev;
       } while (pred.waitStatus > 0);
        
       pred.next = node; 
    }else{
        //ws默认0 当前线程直接走这里
        //修改状态了,把上一个节点的状态直接修改为 -1  直接返回false了
        
        //问题在于,这个方法是在上一个无限循环中调用的,第一次返回了false
        //那么,它还要第二次循环的,第一次把waitStatus从0改成了-1
        //第二次,就直接返回true了
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

非公平锁加锁过程

    //非公平锁
	// 非公平锁和公平锁有一个很大的不一样就是,
	//非公平锁  线程一来就先竞争资源,没有抢到,就去排队了了
    static final class NonfairSync extends Sync {
        
        final void lock() {
            if (compareAndSetState(0, 1)){
                setExclusiveOwnerThread(Thread.currentThread());
            }else{
                acquire(1);
            }
        }
    }

总结:

公平锁和非公平锁:一朝排队永远排队

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值