自己太笨了,总感觉有点绕,就整理下吧~
private Node enq(final Node node) {
//自旋锁
for (;;) {
//tail默认就是null
Node t = tail;
if (t == null) { // Must initialize
//因为tail默认是null,所以首次一定会进来
//compareAndSetHead在下面
//也就是首次一定会把一个新的node设置为head
if (compareAndSetHead(new Node()))
//tail=head=new Node()
tail = head;
//到这里就是tail和head都会指向new Node
} else {
//第二次一定进入else发
//假如此时进入了一个线程A,这个A已经被封装成了node传进来
//当前的node的pre指向t,也就是tail,也就是刚才创建的Node,
//因为第一行就定义了Node t = tail,而t=head=node
node.prev = t;
//这里看下面的compareAndSetTail方法
//把tail节点赋值为新传入的node(Thread A),赋值操作就相当于指向
if (compareAndSetTail(t, node)) {
//这里的t指的是原来的tail节点,tail指向一开始的new Node
//所以就是new Node的next指向新传入的node(Thread A)
t.next = node;
return t;
}
}
}
}
private final boolean compareAndSetHead(Node update) {
//当前的head字段,和null值比对,默认是null,所以相等,所以赋值为update,也就是new node()
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
private final boolean compareAndSetTail(Node expect, Node update) {
//当前的tail字段和期望值exepct,即t进行比较,一定是相等的啊,以为t=tail么,所以更新赋值为update,
//即新传进来的node(Thread A)
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
第一次一定是进入if
当有新的线程进入的时候,进入else(其实没有新的线程进入还是要进入else的),红色的是else所做的操作
总结:
这里面head和tail都是一个node,但是这里我理解为一个类似于指针的东西,它们存在的意义也是为了维持这个双向的链表,没有实际的意义,包括prev和next也是。
还是挺绕的,感觉没必要理解这么透彻,主要思想就是
第一次进来的时候创建这个双向链表的空节点,后面传入新的节点的时候,开始往双向链表后面添加新的节点,而head和tail永远指向第一个和最后一个,中间都是一些切换指针(我这里暂且理解为指针吧,也就是赋值操作,比如tail和head的赋值,prev和next的赋值。)
for(;;)是一个自旋锁,为了不停的去尝试添加节点,但是可能存在并发问题,所以通过cas的方式,添加,可能上次被线程a抢占先机,这次自己还是要去尝试,如何进行不断的尝试?所以通过自旋锁解决,当然如果添加成果最后会通过return跳出自旋锁。
个人认为这个自旋锁和cas的操作用的很厉害,值得学习!
仔细想想为什么这么设计?
因为当前的这个compareAndSetTail和compareAndSetHead,这两个操作,简单说就是为了切换指针指向,简单说就是一“操作”,任何操作,因为在操作系统中,虽然我们目前在解决并发问题,但是也是存在并发问题的,对于这个“操作来说”。所以我们可以选择加锁,也可以选择这种cas自旋锁的方式,因为这个操作可能是很快的,因为可能同时又一万个线程都被阻塞了(极端一点的情况),这个时候,会有很多的切换指针的这个“操作”,反复的加悲观锁的话,没有cas这种乐观锁好。简单总结下,就是这种cas自旋锁的方式能提升性能。
以上全是我的个人理解,可能存在很大的问题,希望没有误导到你,如果有问题还希望您及时指出。