diff --git "a/02.\347\272\277\347\250\213\347\232\204\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234/\347\272\277\347\250\213\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234.md" "b/02.\347\272\277\347\250\213\347\232\204\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234/\347\272\277\347\250\213\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234.md" index 7458d85..c7a1468 100644 --- "a/02.\347\272\277\347\250\213\347\232\204\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234/\347\272\277\347\250\213\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234.md" +++ "b/02.\347\272\277\347\250\213\347\232\204\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234/\347\272\277\347\250\213\347\212\266\346\200\201\350\275\254\346\215\242\344\273\245\345\217\212\345\237\272\346\234\254\346\223\215\344\275\234.md" @@ -52,7 +52,7 @@ - 由于java不能多继承可以实现多个接口,因此,在创建线程的时候尽量多考虑采用实现接口的形式; - 实现callable接口,提交给ExecutorService返回的是异步执行的结果,另外,通常也可以利用FutureTask(Callable callable)将callable进行包装然后FeatureTask提交给ExecutorsService。如图, -![FutureTask接口实现关系](https://github.com/CL0610/Java-concurrency/blob/master/2.%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%8A%B6%E6%80%81%E8%BD%AC%E6%8D%A2%E4%BB%A5%E5%8F%8A%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C/futureTask%E6%8E%A5%E5%8F%A3%E5%AE%9E%E7%8E%B0%E5%85%B3%E7%B3%BB.png) +![FutureTask接口实现关系](https://github.com/CL0610/Java-concurrency/blob/master/02.%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%8A%B6%E6%80%81%E8%BD%AC%E6%8D%A2%E4%BB%A5%E5%8F%8A%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C/futureTask%E6%8E%A5%E5%8F%A3%E5%AE%9E%E7%8E%B0%E5%85%B3%E7%B3%BB.png) 另外由于FeatureTask也实现了Runable接口也可以利用上面第二种方式(实现Runable接口)来新建线程; @@ -61,7 +61,7 @@ # 2. 线程状态转换 # -![线程状态转换图](https://github.com/CL0610/Java-concurrency/blob/master/2.%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%8A%B6%E6%80%81%E8%BD%AC%E6%8D%A2%E4%BB%A5%E5%8F%8A%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C/%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81%E8%BD%AC%E6%8D%A2%E5%85%B3%E7%B3%BB.png) +![线程状态转换图](https://github.com/CL0610/Java-concurrency/blob/master/02.%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%8A%B6%E6%80%81%E8%BD%AC%E6%8D%A2%E4%BB%A5%E5%8F%8A%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C/%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81%E8%BD%AC%E6%8D%A2%E5%85%B3%E7%B3%BB.png) @@ -71,7 +71,7 @@ 用一个表格将上面六种状态进行一个总结归纳。 -![JAVA线程的状态](https://github.com/CL0610/Java-concurrency/blob/master/2.%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%8A%B6%E6%80%81%E8%BD%AC%E6%8D%A2%E4%BB%A5%E5%8F%8A%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C/%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81.png) +![JAVA线程的状态](https://github.com/CL0610/Java-concurrency/blob/master/02.%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%8A%B6%E6%80%81%E8%BD%AC%E6%8D%A2%E4%BB%A5%E5%8F%8A%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C/%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81.png) # 3. 线程状态的基本操作 # @@ -83,7 +83,7 @@ isInterrupted()来感知其他线程对其自身的中断操作,从而做出响应。另外,同样可以调用Thread的静态方法 interrupted()对当前线程进行中断操作,该方法会清除中断标志位。**需要注意的是,当抛出InterruptedException时候,会清除中断标志位,也就是说在调用isInterrupted会返回false。** -![线程中断的方法](https://github.com/CL0610/Java-concurrency/blob/master/2.%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%8A%B6%E6%80%81%E8%BD%AC%E6%8D%A2%E4%BB%A5%E5%8F%8A%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C/%E4%B8%AD%E6%96%AD%E7%BA%BF%E7%A8%8B%E6%96%B9%E6%B3%95.png) +![线程中断的方法](https://github.com/CL0610/Java-concurrency/blob/master/02.%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%8A%B6%E6%80%81%E8%BD%AC%E6%8D%A2%E4%BB%A5%E5%8F%8A%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C/%E4%B8%AD%E6%96%AD%E7%BA%BF%E7%A8%8B%E6%96%B9%E6%B3%95.png) diff --git "a/04.\345\275\273\345\272\225\347\220\206\350\247\243synchronized/java\345\205\263\351\224\256\345\255\227---synchronized.md" "b/04.\345\275\273\345\272\225\347\220\206\350\247\243synchronized/java\345\205\263\351\224\256\345\255\227---synchronized.md" index fe91e9f..78d3720 100644 --- "a/04.\345\275\273\345\272\225\347\220\206\350\247\243synchronized/java\345\205\263\351\224\256\345\255\227---synchronized.md" +++ "b/04.\345\275\273\345\272\225\347\220\206\350\247\243synchronized/java\345\205\263\351\224\256\345\255\227---synchronized.md" @@ -32,7 +32,7 @@ 在java代码中使用synchronized可是使用在代码块和方法中,根据Synchronized用的位置可以有这些使用场景: -![Synchronized的使用场景](https://user-gold-cdn.xitu.io/2018/4/30/16315cc79aaac173?w=700&h=413&f=png&s=33838) +![Synchronized的使用场景](https://github.com/fancycoderzf/Java-concurrency/blob/master/04.%E5%BD%BB%E5%BA%95%E7%90%86%E8%A7%A3synchronized/synchronized%E7%9A%84%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF.png) 如图,synchronized可以用在**方法**上也可以使用在**代码块**中,其中方法是实例方法和静态方法分别锁的是该类的实例对象和该类的对象。而使用在代码块中也可以分为三种,具体的可以看上面的表格。这里的需要注意的是:**如果锁的是类对象的话,尽管new多个实例对象,但他们仍然是属于同一个类依然会被锁住,即线程之间保证同步关系**。 @@ -55,7 +55,7 @@ -![SynchronizedDemo.class](https://user-gold-cdn.xitu.io/2018/4/30/16315cce259af0d2?w=700&h=330&f=png&s=68919) +![SynchronizedDemo.class](https://github.com/fancycoderzf/Java-concurrency/blob/master/04.%E5%BD%BB%E5%BA%95%E7%90%86%E8%A7%A3synchronized/synchronizedDemo.class.png) 如图,上面用黄色高亮的部分就是需要注意的部分了,这也是添Synchronized关键字之后独有的。执行同步代码块后首先要先执行**monitorenter**指令,退出的时候**monitorexit**指令。通过分析之后可以看出,使用Synchronized进行同步,其关键就是必须要对对象的监视器monitor进行获取,当线程获取monitor后才能继续往下执行,否则就只能等待。而这个获取的过程是**互斥**的,即同一时刻只有一个线程能够获取到monitor。上面的demo中在执行完同步代码块之后紧接着再会去执行一个静态同步方法,而这个方法锁的对象依然就这个类对象,那么这个正在执行的线程还需要获取该锁吗?答案是不必的,从上图中就可以看出来,执行静态同步方法的时候就只有一条monitorexit指令,并没有monitorenter获取锁的指令。这就是**锁的重入性**,即在同一锁程中,线程不需要再次获取同一把锁。Synchronized先天具有重入性。**每个对象拥有一个计数器,当线程获取该对象锁后,计数器就会加一,释放锁后就会将计数器减一**。 @@ -64,7 +64,7 @@ 下图表现了对象,对象监视器,同步队列以及执行线程状态之间的关系: -![对象,对象监视器,同步队列和线程状态的关系](https://user-gold-cdn.xitu.io/2018/4/30/16315cd5fa7cf91c?w=700&h=261&f=png&s=54962) +![对象,对象监视器,同步队列和线程状态的关系](https://github.com/fancycoderzf/Java-concurrency/blob/master/04.%E5%BD%BB%E5%BA%95%E7%90%86%E8%A7%A3synchronized/%E5%AF%B9%E8%B1%A1%EF%BC%8C%E5%AF%B9%E8%B1%A1%E7%9B%91%E8%A7%86%E5%99%A8%EF%BC%8C%E5%90%8C%E6%AD%A5%E9%98%9F%E5%88%97%E5%92%8C%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81%E7%9A%84%E5%85%B3%E7%B3%BB.png) 该图可以看出,任意线程对Object的访问,首先要获得Object的监视器,如果获取失败,该线程就进入同步状态,线程状态变为BLOCKED,当Object的监视器占有者释放后,在同步队列中得线程就会有机会重新获取该监视器。 @@ -88,7 +88,7 @@ -![synchronized的happens-before关系](https://user-gold-cdn.xitu.io/2018/4/30/16315ce6ea84f240?w=650&h=629&f=png&s=61572) +![synchronized的happens-before关系](https://github.com/fancycoderzf/Java-concurrency/blob/master/04.%E5%BD%BB%E5%BA%95%E7%90%86%E8%A7%A3synchronized/synchronized%E7%9A%84happens-before%E5%85%B3%E7%B3%BB.png) 在图中每一个箭头连接的两个节点就代表之间的happens-before关系,黑色的是通过程序顺序规则推导出来,红色的为监视器锁规则推导而出:**线程A释放锁happens-before线程B加锁**,蓝色的则是通过程序顺序规则和监视器锁规则推测出来happens-befor关系,通过传递性规则进一步推导的happens-before关系。现在我们来重点关注2 happens-before 5,通过这个关系我们可以得出什么? @@ -100,14 +100,14 @@ 废话不多说依旧先上图。 -![线程A写共享变量](https://user-gold-cdn.xitu.io/2018/4/30/16315cef21fd3ad8?w=557&h=440&f=png&s=10816) +![线程A写共享变量](https://github.com/fancycoderzf/Java-concurrency/blob/master/04.%E5%BD%BB%E5%BA%95%E7%90%86%E8%A7%A3synchronized/%E7%BA%BF%E7%A8%8BA%E5%86%99%E5%85%B1%E4%BA%AB%E5%8F%98%E9%87%8F.png) 从上图可以看出,线程A会首先先从主内存中读取共享变量a=0的值然后将该变量拷贝到自己的本地内存,进行加一操作后,再将该值刷新到主内存,整个过程即为线程A 加锁-->执行临界区代码-->释放锁相对应的内存语义。 -![线程B读共享变量](https://user-gold-cdn.xitu.io/2018/4/30/16315cf41661491e?w=564&h=458&f=png&s=14468) +![线程B读共享变量](https://github.com/fancycoderzf/Java-concurrency/blob/master/04.%E5%BD%BB%E5%BA%95%E7%90%86%E8%A7%A3synchronized/%E7%BA%BF%E7%A8%8BB%E8%AF%BB%E5%85%B1%E4%BA%AB%E5%8F%98%E9%87%8F.png) 线程B获取锁的时候同样会从主内存中共享变量a的值,这个时候就是最新的值1,然后将该值拷贝到线程B的工作内存中去,释放锁的时候同样会重写到主内存中。 @@ -153,13 +153,13 @@ CAS的实现需要硬件指令集的支撑,在JDK1.5后虚拟机才可以使 在同步的时候是获取对象的monitor,即获取到对象的锁。那么对象的锁怎么理解?无非就是类似对对象的一个标志,那么这个标志就是存放在Java对象的对象头。Java对象头里的Mark Word里默认的存放的对象的Hashcode,分代年龄和锁标记位。32为JVM Mark Word默认存储结构为(注:java对象头以及下面的锁状态变化摘自《java并发编程的艺术》一书,该书我认为写的足够好,就没在自己组织语言班门弄斧了): -![Mark Word存储结构](https://user-gold-cdn.xitu.io/2018/4/30/16315cff10307a29?w=700&h=71&f=png&s=23717) +![Mark Word存储结构](https://github.com/fancycoderzf/Java-concurrency/blob/master/04.%E5%BD%BB%E5%BA%95%E7%90%86%E8%A7%A3synchronized/Mark%20Word%E5%AD%98%E5%82%A8%E7%BB%93%E6%9E%84.png) 如图在Mark Word会默认存放hasdcode,年龄值以及锁标志位等信息。 Java SE 1.6中,锁一共有4种状态,级别从低到高依次是:**无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态**,这几个状态会随着竞争情况逐渐升级。**锁可以升级但不能降级**,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。对象的MarkWord变化为下图: -![Mark Word状态变化](https://user-gold-cdn.xitu.io/2018/4/30/16315d056598e4c2?w=700&h=151&f=png&s=47968) +![Mark Word状态变化](https://github.com/fancycoderzf/Java-concurrency/blob/master/04.%E5%BD%BB%E5%BA%95%E7%90%86%E8%A7%A3synchronized/Mark%20Word%E7%8A%B6%E6%80%81%E5%8F%98%E5%8C%96.png) @@ -177,14 +177,14 @@ HotSpot的作者经过研究发现,大多数情况下,锁不仅不存在多 偏向锁使用了一种**等到竞争出现才释放锁**的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。 -![偏向锁撤销流程](https://user-gold-cdn.xitu.io/2018/4/30/16315d0b13b37da4?w=567&h=736&f=png&s=72325) +![偏向锁撤销流程](https://github.com/fancycoderzf/Java-concurrency/blob/master/04.%E5%BD%BB%E5%BA%95%E7%90%86%E8%A7%A3synchronized/%E5%81%8F%E5%90%91%E9%94%81%E6%92%A4%E9%94%80%E6%B5%81%E7%A8%8B.png) 如图,偏向锁的撤销,需要等待**全局安全点**(在这个时间点上没有正在执行的字节码)。它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果线程不处于活动状态,则将对象头设置成无锁状态;如果线程仍然活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word**要么**重新偏向于其他线程,**要么**恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。 下图线程1展示了偏向锁获取的过程,线程2展示了偏向锁撤销的过程。 -![偏向锁获取和撤销流程](https://user-gold-cdn.xitu.io/2018/4/30/16315cb9175365f5?w=630&h=703&f=png&s=160223) +![偏向锁获取和撤销流程](https://github.com/fancycoderzf/Java-concurrency/blob/master/04.%E5%BD%BB%E5%BA%95%E7%90%86%E8%A7%A3synchronized/%E5%81%8F%E5%90%91%E9%94%81%E8%8E%B7%E5%8F%96%E5%92%8C%E6%92%A4%E9%94%80%E6%B5%81%E7%A8%8B.png) > **如何关闭偏向锁** @@ -201,12 +201,12 @@ HotSpot的作者经过研究发现,大多数情况下,锁不仅不存在多 轻量级解锁时,会使用原子的CAS操作将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。下图是两个线程同时争夺锁,导致锁膨胀的流程图。 -![轻量级锁加锁解锁以及锁膨胀](https://user-gold-cdn.xitu.io/2018/4/30/16315cb9193719c2?w=794&h=772&f=png&s=287958) +![轻量级锁加锁解锁以及锁膨胀](https://github.com/fancycoderzf/Java-concurrency/blob/master/04.%E5%BD%BB%E5%BA%95%E7%90%86%E8%A7%A3synchronized/%E8%BD%BB%E9%87%8F%E7%BA%A7%E9%94%81%E5%8A%A0%E9%94%81%E8%A7%A3%E9%94%81%E4%BB%A5%E5%8F%8A%E9%94%81%E8%86%A8%E8%83%80.png) 因为自旋会消耗CPU,为了避免无用的自旋(比如获得锁的线程被阻塞住了),一旦锁升级成重量级锁,就不会再恢复到轻量级锁状态。当锁处于这个状态下,其他线程试图获取锁时,都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程就会进行新一轮的夺锁之争。 ## 3.5 各种锁的比较 ## -![各种锁的对比](https://user-gold-cdn.xitu.io/2018/4/30/16315cb91da523d9?w=800&h=193&f=png&s=116058) +![各种锁的对比](https://github.com/fancycoderzf/Java-concurrency/blob/master/04.%E5%BD%BB%E5%BA%95%E7%90%86%E8%A7%A3synchronized/%E5%90%84%E7%A7%8D%E9%94%81%E7%9A%84%E5%AF%B9%E6%AF%94.png) # 4. 一个例子 # 经过上面的理解,我们现在应该知道了该怎样解决了。更正后的代码为: diff --git "a/15.\345\271\266\345\217\221\345\256\271\345\231\250\344\271\213ConcurrentLinkedQueue/\345\271\266\345\217\221\345\256\271\345\231\250\344\271\213ConcurrentLinkedQueue.md" "b/15.\345\271\266\345\217\221\345\256\271\345\231\250\344\271\213ConcurrentLinkedQueue/\345\271\266\345\217\221\345\256\271\345\231\250\344\271\213ConcurrentLinkedQueue.md" index b1f30b9..001a24d 100644 --- "a/15.\345\271\266\345\217\221\345\256\271\345\231\250\344\271\213ConcurrentLinkedQueue/\345\271\266\345\217\221\345\256\271\345\231\250\344\271\213ConcurrentLinkedQueue.md" +++ "b/15.\345\271\266\345\217\221\345\256\271\345\231\250\344\271\213ConcurrentLinkedQueue/\345\271\266\345\217\221\345\256\271\345\231\250\344\271\213ConcurrentLinkedQueue.md" @@ -29,7 +29,7 @@ head和tail指针会指向一个item域为null的节点,此时ConcurrentLinkedQu -![1.ConcurrentLinkedQueue初始化状态.png](http://upload-images.jianshu.io/upload_images/2615789-a3dbf8f54bb3452e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![1.ConcurrentLinkedQueue初始化状态.png](https://github.com/fancycoderzf/Java-concurrency/blob/master/15.%E5%B9%B6%E5%8F%91%E5%AE%B9%E5%99%A8%E4%B9%8BConcurrentLinkedQueue/ConcurrentLinkedQueue%E5%88%9D%E5%A7%8B%E5%8C%96%E7%8A%B6%E6%80%81.png) @@ -105,7 +105,7 @@ head和tail指针会指向一个item域为null的节点,此时ConcurrentLinkedQu 先从**单线程执行的角度**看起,分析offer 1的过程。第1行代码会对是否为null进行判断,为null的话就直接抛出空指针异常,第2行代码将e包装成一个Node类,第3行为for循环,只有初始化条件没有循环结束条件,这很符合CAS的“套路”,在循环体CAS操作成功会直接return返回,如果CAS操作失败的话就在for循环中不断重试直至成功。这里实例变量t被初始化为tail,p被初始化为t即tail。为了方便下面的理解,**p被认为队列真正的尾节点,tail不一定指向对象真正的尾节点,因为在ConcurrentLinkedQueue中tail是被延迟更新的**,具体原因我们慢慢来看。代码走到第3行的时候,t和p都分别指向初始化时创建的item域为null,next域为null的Node0。第4行变量q被赋值为null,第5行if判断为true,在第7行使用casNext将插入的Node设置成当前队列尾节点p的next节点,如果CAS操作失败,此次循环结束在下次循环中进行重试。CAS操作成功走到第8行,此时p==t,if判断为false,直接return true返回。如果成功插入1的话,此时ConcurrentLinkedQueue的状态如下图所示: -![2.offer 1后队列的状态.png](http://upload-images.jianshu.io/upload_images/2615789-f2509bec71a8dc33.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![2.offer 1后队列的状态.png](https://github.com/fancycoderzf/Java-concurrency/blob/master/15.%E5%B9%B6%E5%8F%91%E5%AE%B9%E5%99%A8%E4%B9%8BConcurrentLinkedQueue/offer%201%E5%90%8E%E9%98%9F%E5%88%97%E7%9A%84%E7%8A%B6%E6%80%81.png) 如图,此时队列的尾节点应该为Node1,而tail指向的节点依然还是Node0,因此可以说明tail是延迟更新的。那么我们继续来看offer 2的时候的情况,很显然此时第4行q指向的节点不为null了,而是指向Node1,第5行if判断为false,第11行if判断为false,代码会走到第13行。好了,**再插入节点的时候我们会问自己这样一个问题?上面已经解释了tail并不是指向队列真正的尾节点,那么在插入节点的时候,我们是不是应该最开始做的就是找到队列当前的尾节点在哪里才能插入?**那么第13行代码就是**找出队列真正的尾节点**。 @@ -114,11 +114,11 @@ head和tail指针会指向一个item域为null的节点,此时ConcurrentLinkedQu p = (p != t && t != (t = tail)) ? t : q; 我们来分析一下这行代码,如果这段代码在**单线程环境**执行时,很显然由于p==t,此时p会被赋值为q,而q等于`Node q = p.next`,即Node1。在第一次循环中指针p指向了队列真正的队尾节点Node1,那么在下一次循环中第4行q指向的节点为null,那么在第5行中if判断为true,那么在第7行依然通过casNext方法设置p节点的next为当前新增的Node,接下来走到第8行,这个时候p!=t,第8行if判断为true,会通过`casTail(t, newNode)`将当前节点Node设置为队列的队尾节点,此时的队列状态示意图如下图所示: -![3.队列offer 2后的状态.png](http://upload-images.jianshu.io/upload_images/2615789-6f8fe58d7a83fe61.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![3.队列offer 2后的状态.png](https://github.com/fancycoderzf/Java-concurrency/blob/master/15.%E5%B9%B6%E5%8F%91%E5%AE%B9%E5%99%A8%E4%B9%8BConcurrentLinkedQueue/%E9%98%9F%E5%88%97offer%202%E5%90%8E%E7%9A%84%E7%8A%B6%E6%80%81.png) **tail指向的节点由Node0改变为Node2**,这里的casTail失败不需要重试的原因是,offer代码中主要是通过p的next节点q(`Node q = p.next`)决定后面的逻辑走向的,当casTail失败时状态示意图如下: -![4.队列进行入队操作后casTail失败后的状态图.png](http://upload-images.jianshu.io/upload_images/2615789-3b07de9df192dfc7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![4.队列进行入队操作后casTail失败后的状态图.png](https://github.com/fancycoderzf/Java-concurrency/blob/master/15.%E5%B9%B6%E5%8F%91%E5%AE%B9%E5%99%A8%E4%B9%8BConcurrentLinkedQueue/%E9%98%9F%E5%88%97%E8%BF%9B%E8%A1%8C%E5%85%A5%E9%98%9F%E6%93%8D%E4%BD%9C%E5%90%8EcasTail%E5%A4%B1%E8%B4%A5%E5%90%8E%E7%9A%84%E7%8A%B6%E6%80%81%E5%9B%BE.png) 如图,**如果这里casTail设置tail失败即tail还是指向Node0节点的话,无非就是多循环几次通过13行代码定位到队尾节点**。 @@ -141,7 +141,7 @@ head和tail指针会指向一个item域为null的节点,此时ConcurrentLinkedQu -![5.线程A和线程B有可能的执行时序.png](http://upload-images.jianshu.io/upload_images/2615789-9fd7db3a6c9372ff.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![5.线程A和线程B有可能的执行时序.png](https://github.com/fancycoderzf/Java-concurrency/blob/master/15.%E5%B9%B6%E5%8F%91%E5%AE%B9%E5%99%A8%E4%B9%8BConcurrentLinkedQueue/%E7%BA%BF%E7%A8%8BA%E5%92%8C%E7%BA%BF%E7%A8%8BB%E6%9C%89%E5%8F%AF%E8%83%BD%E7%9A%84%E6%89%A7%E8%A1%8C%E6%97%B6%E5%BA%8F.png) 如图,假设线程A此时读取了变量t,线程B刚好在这个时候offer一个Node后,此时会修改tail指针,那么这个时候线程A再次执行t=tail时t会指向另外一个节点,很显然线程A前后两次读取的变量t指向的节点不相同,即`t != (t = tail)`为true,并且由于t指向节点的变化`p != t`也为true,此时该行代码的执行结果为p和t最新的t指针指向了同一个节点,并且此时t也是队列真正的对尾节点。那么,现在已经定位到队列真正的队尾节点,就可以执行offer操作了。 @@ -181,14 +181,14 @@ poll方法源码如下: 我们还是先站在**单线程的角度**去理清该方法的基本逻辑。假设ConcurrentLinkedQueue初始状态如下图所示: -![6.队列初始状态.png](http://upload-images.jianshu.io/upload_images/2615789-450e7301fd19e6df.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![6.队列初始状态.png](https://github.com/fancycoderzf/Java-concurrency/blob/master/15.%E5%B9%B6%E5%8F%91%E5%AE%B9%E5%99%A8%E4%B9%8BConcurrentLinkedQueue/%E9%98%9F%E5%88%97%E5%88%9D%E5%A7%8B%E7%8A%B6%E6%80%81.png) 参数offer时的定义,我们还是先将**变量p作为队列要删除真正的队头节点,h(head)指向的节点并不一定是队列的队头节点**。先来看poll出Node1时的情况,由于`p=h=head`,参照上图,很显然此时p指向的Node1的数据域不为null,在第4行代码中`item!=null`判断为true后接下来通过`casItem`将Node1的数据域设置为null。如果CAS设置失败则此次循环结束等待下一次循环进行重试。若第4行执行成功进入到第5行代码,此时p和h都指向Node1,第5行if判断为false,然后直接到第7行return回Node1的数据域1,方法运行结束,此时的队列状态如下图。 -![7.队列出队操作后的状态.png](http://upload-images.jianshu.io/upload_images/2615789-c3c45ac89c461ab5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![7.队列出队操作后的状态.png](https://github.com/fancycoderzf/Java-concurrency/blob/master/15.%E5%B9%B6%E5%8F%91%E5%AE%B9%E5%99%A8%E4%B9%8BConcurrentLinkedQueue/%E9%98%9F%E5%88%97%E5%87%BA%E9%98%9F%E6%93%8D%E4%BD%9C%E5%90%8E%E7%9A%84%E7%8A%B6%E6%80%81.png) 下面继续从队列中poll,很显然当前h和p指向的Node1的数据域为null,那么第一件事就是要**定位准备删除的队头节点(找到数据域不为null的节点)**。 @@ -200,7 +200,7 @@ poll方法源码如下: -![8.经过一次循环后的状态.png](http://upload-images.jianshu.io/upload_images/2615789-c4deb3237eefb777.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![8.经过一次循环后的状态.png](https://github.com/fancycoderzf/Java-concurrency/blob/master/15.%E5%B9%B6%E5%8F%91%E5%AE%B9%E5%99%A8%E4%B9%8BConcurrentLinkedQueue/%E7%BB%8F%E8%BF%87%E4%B8%80%E6%AC%A1%E5%BE%AA%E7%8E%AF%E5%90%8E%E7%9A%84%E7%8A%B6%E6%80%81.png) 进行下一次循环,第4行的操作同上述,当前假设第4行中casItem设置成功,由于p已经指向了Node2,而h还依旧指向Node1,此时第5行的if判断为true,然后执行`updateHead(h, ((q = p.next) != null) ? q : p)`,此时q指向的Node3,所有传入updateHead方法的分别是指向Node1的h引用和指向Node3的q引用。updateHead方法的源码为: @@ -212,7 +212,7 @@ poll方法源码如下: 该方法主要是通过`casHead`将队列的head指向Node3,并且通过 `h.lazySetNext`将Node1的next域指向它自己。最后在第7行代码中返回Node2的值。此时队列的状态如下图所示: -![9.Node2从队列中出队后的状态.png](http://upload-images.jianshu.io/upload_images/2615789-5a93cb7a44f40745.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![9.Node2从队列中出队后的状态.png](https://github.com/fancycoderzf/Java-concurrency/blob/master/15.%E5%B9%B6%E5%8F%91%E5%AE%B9%E5%99%A8%E4%B9%8BConcurrentLinkedQueue/Node2%E4%BB%8E%E9%98%9F%E5%88%97%E4%B8%AD%E5%87%BA%E9%98%9F%E5%90%8E%E7%9A%84%E7%8A%B6%E6%80%81.png) @@ -269,9 +269,9 @@ Node1的next域指向它自己,head指向了Node3。如果队列为空队列 > **offer->poll->offer** 在offer方法的第11行代码`if (p == q)`,能够让if判断为true的情况为p指向的节点为**哨兵节点**,而什么时候会构造哨兵节点呢?在对poll方法的讨论中,我们已经找到了答案,即**当head指向的节点的item域为null时会寻找真正的队头节点,等到待插入的节点插入之后,会更新head,并且将原来head指向的节点设置为哨兵节点。**假设队列初始状态如下图所示: -![10.offer和poll相互影响分析时队列初始状态.png](http://upload-images.jianshu.io/upload_images/2615789-70b0af25bced807a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![10.offer和poll相互影响分析时队列初始状态.png](https://github.com/fancycoderzf/Java-concurrency/blob/master/15.%E5%B9%B6%E5%8F%91%E5%AE%B9%E5%99%A8%E4%B9%8BConcurrentLinkedQueue/offer%E5%92%8Cpoll%E7%9B%B8%E4%BA%92%E5%BD%B1%E5%93%8D%E5%88%86%E6%9E%90%E6%97%B6%E9%98%9F%E5%88%97%E5%88%9D%E5%A7%8B%E7%8A%B6%E6%80%81.png) 因此在线程A执行offer时,线程B执行poll就会存在如下一种情况: -![11.线程A和线程B可能存在的执行时序.png](http://upload-images.jianshu.io/upload_images/2615789-cf872ba6fdd99099.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![11.线程A和线程B可能存在的执行时序.png](https://github.com/fancycoderzf/Java-concurrency/blob/master/15.%E5%B9%B6%E5%8F%91%E5%AE%B9%E5%99%A8%E4%B9%8BConcurrentLinkedQueue/%E7%BA%BF%E7%A8%8BA%E5%92%8C%E7%BA%BF%E7%A8%8BB%E5%8F%AF%E8%83%BD%E5%AD%98%E5%9C%A8%E7%9A%84%E6%89%A7%E8%A1%8C%E6%97%B6%E5%BA%8F.png) @@ -283,7 +283,7 @@ Node1的next域指向它自己,head指向了Node3。如果队列为空队列 -![12.线程B进行poll后队列的状态图.png](http://upload-images.jianshu.io/upload_images/2615789-d0d2d16b16c11802.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![12.线程B进行poll后队列的状态图.png](https://github.com/fancycoderzf/Java-concurrency/blob/master/15.%E5%B9%B6%E5%8F%91%E5%AE%B9%E5%99%A8%E4%B9%8BConcurrentLinkedQueue/%E7%BA%BF%E7%A8%8BB%E8%BF%9B%E8%A1%8Cpoll%E5%90%8E%E9%98%9F%E5%88%97%E7%9A%84%E7%8A%B6%E6%80%81%E5%9B%BE.png) 此时线程A在执行判断`if (p == q)`时就为true,会继续执行` p = (t != (t = tail)) ? t : head;`,由于tail指针没有发生改变所以p被赋值为head,重新从head开始完成插入操作。 diff --git "a/19.\345\271\266\345\217\221\345\256\271\345\231\250\344\271\213BlockingQueue/\345\271\266\345\217\221\345\256\271\345\231\250\344\271\213BlockingQueue.md" "b/19.\345\271\266\345\217\221\345\256\271\345\231\250\344\271\213BlockingQueue/\345\271\266\345\217\221\345\256\271\345\231\250\344\271\213BlockingQueue.md" index 8d0f280..c79c075 100644 --- "a/19.\345\271\266\345\217\221\345\256\271\345\231\250\344\271\213BlockingQueue/\345\271\266\345\217\221\345\256\271\345\231\250\344\271\213BlockingQueue.md" +++ "b/19.\345\271\266\345\217\221\345\256\271\345\231\250\344\271\213BlockingQueue/\345\271\266\345\217\221\345\256\271\345\231\250\344\271\213BlockingQueue.md" @@ -8,7 +8,7 @@ BlockingQueue基本操作总结如下(此图来源于JAVA API文档): -![BlockingQueue基本操作.png](http://upload-images.jianshu.io/upload_images/2615789-19d06e0ba334fe52.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![BlockingQueue基本操作.png](https://github.com/fancycoderzf/Java-concurrency/blob/master/19.%E5%B9%B6%E5%8F%91%E5%AE%B9%E5%99%A8%E4%B9%8BBlockingQueue/BlockingQueue%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C.png) BlockingQueue继承于Queue接口,因此,对数据元素的基本操作有: @@ -88,7 +88,7 @@ tryTransfer方法如果当前有消费者线程(调用take方法或者具有 LinkedBlockingDeque是基于链表数据结构的有界阻塞双端队列,如果在创建对象时为指定大小时,其默认大小为Integer.MAX_VALUE。与LinkedBlockingQueue相比,主要的不同点在于,LinkedBlockingDeque具有双端队列的特性。LinkedBlockingDeque基本操作如下图所示(来源于java文档) -![LinkedBlockingDeque的基本操作.png](http://upload-images.jianshu.io/upload_images/2615789-d51d940d30786e32.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/600) +![LinkedBlockingDeque的基本操作.png](https://github.com/fancycoderzf/Java-concurrency/blob/master/19.%E5%B9%B6%E5%8F%91%E5%AE%B9%E5%99%A8%E4%B9%8BBlockingQueue/LinkedBlockingDeque%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C.png) @@ -97,7 +97,7 @@ LinkedBlockingDeque是基于链表数据结构的有界阻塞双端队列,如 另外,LinkedBlockingDeque实现了BlockingDueue接口而LinkedBlockingQueue实现的是BlockingQueue,这两个接口的主要区别如下图所示(来源于java文档): -![BlockingQueue和BlockingDeque的区别.png](http://upload-images.jianshu.io/upload_images/2615789-7316a2543b99caa2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/600) +![BlockingQueue和BlockingDeque的区别.png](https://github.com/fancycoderzf/Java-concurrency/blob/master/19.%E5%B9%B6%E5%8F%91%E5%AE%B9%E5%99%A8%E4%B9%8BBlockingQueue/BlockingQueue%E5%92%8CBlockingDeque%E7%9A%84%E5%8C%BA%E5%88%AB.png) 从上图可以看出,两个接口的功能是可以等价使用的,比如BlockingQueue的add方法和BlockingDeque的addLast方法的功能是一样的。