详细分析地址:跳转
head等于NodeA,NodeA指向NodeB,NodeB指向NodeC,
tail等于NodeC。
NodeA也就是持有线程的Node,阻塞队列是指NodeA后面的所有队列,NodeA不属于阻塞队列。
公平锁:
A线程先进来,调用tryAcquire,此时没有对象持有锁,A拿到锁开始执行任务,不创建Node,结束。此时A还没有释放锁。
B线程进来,调用tryAcquire,发现锁有被线程占用了,创建一个NodeB,此时head和tail都没有指向,创建一个空的Node,在把当前的NodeB追加到这个空Node后面。然后head指向这个空Node,tail指向这个NodeB。
这个时候来到acquireQueued方法,进到for,因为NodeB的前一个节点等于Head,所以尝试或获取锁,此时A线程还没释放锁,那么就会获取失败,然后就来到了shouldParkAfterFailedAcquire方法,在shouldParkAfterFailedAcquire里面会把NodeB的前一个节点,也就是空Node状态置为-1,接着返回false,然后又一次for循环。还是获取锁失败,在进到shouldParkAfterFailedAcquire,不过此时空Node是-1,此时NodeB,就会开始睡眠。所以在Node集合里面,Heade也就是空Node虽然等于-1,但是没有睡眠,而空Node的下一个节点NodeB,才是睡眠的那个,所以阻塞队列是从NodeB开始算的,空Node不能是阻塞队列。
后面有线程C进来,NodeC追加到线程B后面,并且把B的waitStatus改成了-1,进来的线程D也是一样,追加到后面。当线程A释放锁时,这个时候会拿到Heade这个节点,当然此时Hade还是空Node,waitStatus等于-1,将其改为0,然后拿到空Node的下一个Node,也就是NodeB,不等于Null,然后唤醒NodeB,
此时NodeB又回到了acquireQueued方法里面,又尝试获取锁,这个时候,因为锁时空的,所以NodeB拿到了锁,并且NodeB变成了Head,然后线程B得到运行,此时阻塞队列就要从NodeC开始算了。这个时候真正有执行权的Head这个节点,一开始因为Head指向的空Node,所以执行权不在这个空Node上面,而是在线程A上。
然后B释放锁,此时aqs的status变成0,拿到Head元素,是NodeB,waitSatus=-1,进到unparkSuccessor里面,先将NodeB的waitStatus状态置为0,然后拿到NodeB的下一个线程NodeC,唤醒NodeC。依次循环。
小结:
所以在Node集合里面Node1,Node2, Node3 ,Node4。。。。。。NodeN
1、其中Head等于Node1, tail=NodeN,
2、持有锁的线程,就是Head,也就是Node1,也就是他不是睡眠,后面的Node,从Node2到NodeN都可以成为阻塞队列。每次唤醒的都是Head的后一个元素。也就是Node集合里面的第二个元素。
2、每次持有锁的线程都是Head,这里有个特殊时候,就是刚开始Node集合是空的时候,线程B进来发现Node集合是空的,所以会创建的空的Node,并且这个空的Node是Head,这样我们前面的说的“每次持有锁的线程都是Head”就不成立了,因为此时持有锁的线程是线程A,而A一开始是没有创建Node的。