非阻塞同步算法与CAS(Compare and Swap)无锁算法

锁(lock)的代价

锁是用来做并发最简单的方式,当然其代价也是最高的。内核态的锁的时候需要操作系统进行一次上下文切换,加锁、释放锁会导致比较多的上下文切换和调度延时,等待锁的线程会被挂起直至锁释放。在上下文切换的时候,cpu之前缓存的指令和数据都将失效,对性能有很大的损失。操作系统对多线程的锁进行判断就像两姐妹在为一个玩具在争吵,然后操作系统就是能决定他们谁能拿到玩具的父母,这是很慢的。用户态的锁虽然避免了这些问题,但是其实它们只是在没有真实的竞争时才有效。

Java在JDK1.5之前都是靠synchronized关键字保证同步的,这种通过使用一致的锁定协议来协调对共享状态的访问,可以确保无论哪个线程持有守护变量的锁,都采用独占的方式来访问这些变量,如果出现多个线程同时访问锁,那第一些线线程将被挂起,当线程恢复执行时,必须等待其它线程执行完他们的时间片以后才能被调度执行,在挂起和恢复执行过程中存在着很大的开销。锁还存在着其它一些缺点,当一个线程正在等待锁时,它不能做任何事。如果一个线程在持有锁的情况下被延迟执行,那么所有需要这个锁的线程都无法执行下去。如果被阻塞的线程优先级高,而持有锁的线程优先级低,将会导致优先级反转(Priority Inversion)。

乐观锁与悲观锁

独占锁是一种悲观锁,synchronized就是一种独占锁,它假设最坏的情况,并且只有在确保其它线程不会造成干扰的情况下执行,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。而另一个更加有效的锁就是乐观锁。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。

volatile的问题

与锁相比,volatile变量是一和更轻量级的同步机制,因为在使用这些变量时不会发生上下文切换和线程调度等操作,但是volatile变量也存在一些局限:不能用于构建原子的复合操作,因此当一个变量依赖旧值时就不能使用volatile变量。(参考:谈谈volatiile

volatile只能保证变量对各个线程的可见性,但不能保证原子性。为什么?见我的另外一篇文章:《为什么volatile不能保证原子性而Atomic可以?

Java中的原子操作( atomic operations)

原子操作指的是在一步之内就完成而且不能被中断。原子操作在多线程环境中是线程安全的,无需考虑同步的问题。在java中,下列操作是原子操作:

  • all assignments of primitive types except for long and double
  • all assignments of references
  • all operations of java.concurrent.Atomic* classes
  • all assignments to volatile longs and doubles

问题来了,为什么long型赋值不是原子操作呢?例如:

1
long  foo = 65465498L;

实时上java会分两步写入这个long变量,先写32位,再写后32位。这样就线程不安全了。如果改成下面的就线程安全了:

1
private  volatile  long  foo;

因为volatile内部已经做了synchronized.

CAS无锁算法

要实现无锁(lock-free)的非阻塞算法有多种实现方法,其中CAS(比较与交换,Compare and swap)是一种有名的无锁算法。CAS, CPU指令,在大多数处理器架构,包括IA32、Space中采用的都是CAS指令,CAS的语义是“我认为V的值应该为A,如果是,那么将V的值更新为B,否则不修改并告诉V的值实际为多少”,CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。CAS无锁算法的C实现如下:

1
2
3
4
5
6
7
8
9
int  compare_and_swap ( int * reg, int  oldval, int  newval)
{
   ATOMIC();
   int  old_reg_val = *reg;
   if  (old_reg_val == oldval)
      *reg = newval;
   END_ATOMIC();
   return  old_reg_val;
}
CAS(乐观锁算法)的基本假设前提

CAS比较与交换的伪代码可以表示为:

do{   
       备份旧数据;  
       基于旧数据构造新数据;  
}while(!CAS( 内存地址,备份的旧数据,新数据 ))  

ConcurrencyCAS 

(上图的解释:CPU去更新一个值,但如果想改的值不再是原来的值,操作就失败,因为很明显,有其它操作先改变了这个值。)

就是指当两者进行比较时,如果相等,则证明共享数据没有被修改,替换成新值,然后继续往下运行;如果不相等,说明共享数据已经被修改,放弃已经所做的操作,然后重新执行刚才的操作。容易看出 CAS 操作是基于共享数据不会被修改的假设,采用了类似于数据库的 commit-retry 的模式。当同步冲突出现的机会很少时,这种假设能带来较大的性能提升。

CAS的开销(CPU Cache Miss problem)

前面说过了,CAS(比较并交换)是CPU指令级的操作,只有一步原子操作,所以非常快。而且CAS避免了请求操作系统来裁定锁的问题,不用麻烦操作系统,直接在CPU内部就搞定了。但CAS就没有开销了吗?不!有cache miss的情况。这个问题比较复杂,首先需要了解CPU的硬件体系结构:

2014-02-19_11h35_45

上图可以看到一个8核CPU计算机系统,每个CPU有cache(CPU内部的高速缓存,寄存器),管芯内还带有一个互联模块,使管芯内的两个核可以互相通信。在图中央的系统互联模块可以让四个管芯相互通信,并且将管芯与主存连接起来。数据以“缓存线”为单位在系统中传输,“缓存线”对应于内存中一个 2 的幂大小的字节块,大小通常为 32 到 256 字节之间。当 CPU 从内存中读取一个变量到它的寄存器中时,必须首先将包含了该变量的缓存线读取到 CPU 高速缓存。同样地,CPU 将寄存器中的一个值存储到内存时,不仅必须将包含了该值的缓存线读到 CPU 高速缓存,还必须确保没有其他 CPU 拥有该缓存线的拷贝。

比如,如果 CPU0 在对一个变量执行“比较并交换”(CAS)操作,而该变量所在的缓存线在 CPU7 的高速缓存中,就会发生以下经过简化的事件序列:

  • CPU0 检查本地高速缓存,没有找到缓存线。
  • 请求被转发到 CPU0 和 CPU1 的互联模块,检查 CPU1 的本地高速缓存,没有找到缓存线。
  • 请求被转发到系统互联模块,检查其他三个管芯,得知缓存线被 CPU6和 CPU7 所在的管芯持有。
  • 请求被转发到 CPU6 和 CPU7 的互联模块,检查这两个 CPU 的高速缓存,在 CPU7 的高速缓存中找到缓存线。
  • CPU7 将缓存线发送给所属的互联模块,并且刷新自己高速缓存中的缓存线。
  • CPU6 和 CPU7 的互联模块将缓存线发送给系统互联模块。
  • 系统互联模块将缓存线发送给 CPU0 和 CPU1 的互联模块。
  • CPU0 和 CPU1 的互联模块将缓存线发送给 CPU0 的高速缓存。
  • CPU0 现在可以对高速缓存中的变量执行 CAS 操作了

以上是刷新不同CPU缓存的开销。最好情况下的 CAS 操作消耗大概 40 纳秒,超过 60 个时钟周期。这里的“最好情况”是指对某一个变量执行 CAS 操作的 CPU 正好是最后一个操作该变量的CPU,所以对应的缓存线已经在 CPU 的高速缓存中了,类似地,最好情况下的锁操作(一个“round trip 对”包括获取锁和随后的释放锁)消耗超过 60 纳秒,超过 100 个时钟周期。这里的“最好情况”意味着用于表示锁的数据结构已经在获取和释放锁的 CPU 所属的高速缓存中了。锁操作比 CAS 操作更加耗时,是因深入理解并行编程 
为锁操作的数据结构中需要两个原子操作。缓存未命中消耗大概 140 纳秒,超过 200 个时钟周期。需要在存储新值时查询变量的旧值的 CAS 操作,消耗大概 300 纳秒,超过 500 个时钟周期。想想这个,在执行一次 CAS 操作的时间里,CPU 可以执行 500 条普通指令。这表明了细粒度锁的局限性。

以下是cache miss cas 和lock的性能对比:

2014-02-19_11h43_23

JVM对CAS的支持:AtomicInt, AtomicLong.incrementAndGet()

在JDK1.5之前,如果不编写明确的代码就无法执行CAS操作,在JDK1.5中引入了底层的支持,在int、long和对象的引用等类型上都公开了CAS的操作,并且JVM把它们编译为底层硬件提供的最有效的方法,在运行CAS的平台上,运行时把它们编译为相应的机器指令,如果处理器/CPU不支持CAS指令,那么JVM将使用自旋锁。因此,值得注意的是,CAS解决方案与平台/编译器紧密相关(比如x86架构下其对应的汇编指令是lock cmpxchg,如果想要64Bit的交换,则应使用lock cmpxchg8b。在.NET中我们可以使用Interlocked.CompareExchange函数)

在原子类变量中,如java.util.concurrent.atomic中的AtomicXXX,都使用了这些底层的JVM支持为数字类型的引用类型提供一种高效的CAS操作,而在java.util.concurrent中的大多数类在实现时都直接或间接的使用了这些原子变量类。

Java 1.6中AtomicLong.incrementAndGet()的实现源码为:

由此可见,AtomicLong.incrementAndGet的实现用了乐观锁技术,调用了sun.misc.Unsafe类库里面的 CAS算法,用CPU指令来实现无锁自增。所以,AtomicLong.incrementAndGet的自增比用synchronized的锁效率倍增。

1
2
3
4
5
6
7
8
9
10
11
12
public  final  int  getAndIncrement() { 
         for  (;;) { 
             int  current = get(); 
             int  next = current + 1
             if  (compareAndSet(current, next)) 
                 return  current; 
        
   
public  final  boolean  compareAndSet( int  expect, int  update) { 
     return  unsafe.compareAndSwapInt( this , valueOffset, expect, update); 
}

下面是测试代码:可以看到用AtomicLong.incrementAndGet的性能比用synchronized高出几倍。

2014-02-12_14h56_39

CAS的例子:非阻塞堆栈

下面是比非阻塞自增稍微复杂一点的CAS的例子:非阻塞堆栈/ConcurrentStack 。ConcurrentStack 中的 push() 和 pop() 操作在结构上与NonblockingCounter 上相似,只是做的工作有些冒险,希望在 “提交” 工作的时候,底层假设没有失效。push() 方法观察当前最顶的节点,构建一个新节点放在堆栈上,然后,如果最顶端的节点在初始观察之后没有变化,那么就安装新节点。如果 CAS 失败,意味着另一个线程已经修改了堆栈,那么过程就会重新开始。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public  class  ConcurrentStack<E> {
     AtomicReference<Node<E>> head = new  AtomicReference<Node<E>>();
     public  void  push(E item) {
         Node<E> newHead = new  Node<E>(item);
         Node<E> oldHead;
         do  {
             oldHead = head.get();
             newHead.next = oldHead;
         } while  (!head.compareAndSet(oldHead, newHead));
     }
     public  E pop() {
         Node<E> oldHead;
         Node<E> newHead;
         do  {
             oldHead = head.get();
             if  (oldHead == null )
                 return  null ;
             newHead = oldHead.next;
         } while  (!head.compareAndSet(oldHead,newHead));
         return  oldHead.item;
     }
     static  class  Node<E> {
         final  E item;
         Node<E> next;
         public  Node(E item) { this .item = item; }
     }
}

在轻度到中度的争用情况下,非阻塞算法的性能会超越阻塞算法,因为 CAS 的多数时间都在第一次尝试时就成功,而发生争用时的开销也不涉及线程挂起和上下文切换,只多了几个循环迭代。没有争用的 CAS 要比没有争用的锁便宜得多(这句话肯定是真的,因为没有争用的锁涉及 CAS 加上额外的处理),而争用的 CAS 比争用的锁获取涉及更短的延迟。

在高度争用的情况下(即有多个线程不断争用一个内存位置的时候),基于锁的算法开始提供比非阻塞算法更好的吞吐率,因为当线程阻塞时,它就会停止争用,耐心地等候轮到自己,从而避免了进一步争用。但是,这么高的争用程度并不常见,因为多数时候,线程会把线程本地的计算与争用共享数据的操作分开,从而给其他线程使用共享数据的机会。

CAS的例子3:非阻塞链表

以上的示例(自增计数器和堆栈)都是非常简单的非阻塞算法,一旦掌握了在循环中使用 CAS,就可以容易地模仿它们。对于更复杂的数据结构,非阻塞算法要比这些简单示例复杂得多,因为修改链表、树或哈希表可能涉及对多个指针的更新。CAS 支持对单一指针的原子性条件更新,但是不支持两个以上的指针。所以,要构建一个非阻塞的链表、树或哈希表,需要找到一种方式,可以用 CAS 更新多个指针,同时不会让数据结构处于不一致的状态。

在链表的尾部插入元素,通常涉及对两个指针的更新:“尾” 指针总是指向列表中的最后一个元素,“下一个” 指针从过去的最后一个元素指向新插入的元素。因为需要更新两个指针,所以需要两个 CAS。在独立的 CAS 中更新两个指针带来了两个需要考虑的潜在问题:如果第一个 CAS 成功,而第二个 CAS 失败,会发生什么?如果其他线程在第一个和第二个 CAS 之间企图访问链表,会发生什么?

对于非复杂数据结构,构建非阻塞算法的 “技巧” 是确保数据结构总处于一致的状态(甚至包括在线程开始修改数据结构和它完成修改之间),还要确保其他线程不仅能够判断出第一个线程已经完成了更新还是处在更新的中途,还能够判断出如果第一个线程走向 AWOL,完成更新还需要什么操作。如果线程发现了处在更新中途的数据结构,它就可以 “帮助” 正在执行更新的线程完成更新,然后再进行自己的操作。当第一个线程回来试图完成自己的更新时,会发现不再需要了,返回即可,因为 CAS 会检测到帮助线程的干预(在这种情况下,是建设性的干预)。

这种 “帮助邻居” 的要求,对于让数据结构免受单个线程失败的影响,是必需的。如果线程发现数据结构正处在被其他线程更新的中途,然后就等候其他线程完成更新,那么如果其他线程在操作中途失败,这个线程就可能永远等候下去。即使不出现故障,这种方式也会提供糟糕的性能,因为新到达的线程必须放弃处理器,导致上下文切换,或者等到自己的时间片过期(而这更糟)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public  class  LinkedQueue <E> {
     private  static  class  Node <E> {
         final  E item;
         final  AtomicReference<Node<E>> next;
         Node(E item, Node<E> next) {
             this .item = item;
             this .next = new  AtomicReference<Node<E>>(next);
         }
     }
     private  AtomicReference<Node<E>> head
         = new  AtomicReference<Node<E>>( new  Node<E>( null , null ));
     private  AtomicReference<Node<E>> tail = head;
     public  boolean  put(E item) {
         Node<E> newNode = new  Node<E>(item, null );
         while  ( true ) {
             Node<E> curTail = tail.get();
             Node<E> residue = curTail.next.get();
             if  (curTail == tail.get()) {
                 if  (residue == null ) /* A */  {
                     if  (curTail.next.compareAndSet( null , newNode)) /* C */  {
                         tail.compareAndSet(curTail, newNode) /* D */  ;
                         return  true ;
                     }
                 } else  {
                     tail.compareAndSet(curTail, residue) /* B */ ;
                 }
             }
         }
     }
}

具体算法相见IBM Developerworks

Java的ConcurrentHashMap的实现原理

Java5中的ConcurrentHashMap,线程安全,设计巧妙,用桶粒度的锁,避免了put和get中对整个map的锁定,尤其在get中,只对一个HashEntry做锁定操作,性能提升是显而易见的。

8aea11a8-4184-3f1f-aba7-169aa5e0797a

具体实现中使用了锁分离机制,在这个帖子中有非常详细的讨论。这里有关于Java内存模型结合ConcurrentHashMap的分析。以下是JDK6的ConcurrentHashMap的源码:

Java的ConcurrentLinkedQueue实现方法

ConcurrentLinkedQueue也是同样使用了CAS指令,但其性能并不高因为太多CAS操作。其源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
/*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.  Oracle designates this
  * particular file as subject to the "Classpath" exception as provided
  * by Oracle in the LICENSE file that accompanied this code.
  *
  * This code is distributed in the hope that it will be useful, but WITHOUT
  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  * version 2 for more details (a copy is included in the LICENSE file that
  * accompanied this code).
  *
  * You should have received a copy of the GNU General Public License version
  * 2 along with this work; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  *
  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  * or visit www.oracle.com if you need additional information or have any
  * questions.
  */
 
/*
  * This file is available under and governed by the GNU General Public
  * License version 2 only, as published by the Free Software Foundation.
  * However, the following notice accompanied the original version of this
  * file:
  *
  * Written by Doug Lea with assistance from members of JCP JSR-166
  * Expert Group and released to the public domain, as explained at
  */
 
package  java.util.concurrent;
import  java.util.*;
import  java.util.concurrent.atomic.*;
 
 
/**
  * An unbounded thread-safe {@linkplain Queue queue} based on linked nodes.
  * This queue orders elements FIFO (first-in-first-out).
  * The <em>head</em> of the queue is that element that has been on the
  * queue the longest time.
  * The <em>tail</em> of the queue is that element that has been on the
  * queue the shortest time. New elements
  * are inserted at the tail of the queue, and the queue retrieval
  * operations obtain elements at the head of the queue.
  * A <tt>ConcurrentLinkedQueue</tt> is an appropriate choice when
  * many threads will share access to a common collection.
  * This queue does not permit <tt>null</tt> elements.
  *
  * <p>This implementation employs an efficient &quot;wait-free&quot;
  * algorithm based on one described in <a
  * Fast, and Practical Non-Blocking and Blocking Concurrent Queue
  * Algorithms</a> by Maged M. Michael and Michael L. Scott.
  *
  * <p>Beware that, unlike in most collections, the <tt>size</tt> method
  * is <em>NOT</em> a constant-time operation. Because of the
  * asynchronous nature of these queues, determining the current number
  * of elements requires a traversal of the elements.
  *
  * <p>This class and its iterator implement all of the
  * <em>optional</em> methods of the {@link Collection} and {@link
  * Iterator} interfaces.
  *
  * <p>Memory consistency effects: As with other concurrent
  * collections, actions in a thread prior to placing an object into a
  * {@code ConcurrentLinkedQueue}
  * <a href="package-summary.html#MemoryVisibility"><i>happen-before</i></a>
  * actions subsequent to the access or removal of that element from
  * the {@code ConcurrentLinkedQueue} in another thread.
  *
  * <p>This class is a member of the
  * <a href="{@docRoot}/../technotes/guides/collections/index.html">
  * Java Collections Framework</a>.
  *
  * @since 1.5
  * @author Doug Lea
  * @param <E> the type of elements held in this collection
  *
  */
public  class  ConcurrentLinkedQueue<E> extends  AbstractQueue<E>
         implements  Queue<E>, java.io.Serializable {
     private  static  final  long  serialVersionUID = 196745693267521676L;
 
     /*
      * This is a straight adaptation of Michael & Scott algorithm.
      * For explanation, read the paper.  The only (minor) algorithmic
      * difference is that this version supports lazy deletion of
      * internal nodes (method remove(Object)) -- remove CAS'es item
      * fields to null. The normal queue operations unlink but then
      * pass over nodes with null item fields. Similarly, iteration
      * methods ignore those with nulls.
      *
      * Also note that like most non-blocking algorithms in this
      * package, this implementation relies on the fact that in garbage
      * collected systems, there is no possibility of ABA problems due
      * to recycled nodes, so there is no need to use "counted
      * pointers" or related techniques seen in versions used in
      * non-GC'ed settings.
      */
 
     private  static  class  Node<E> {
         private  volatile  E item;
         private  volatile  Node<E> next;
 
         private  static  final
             AtomicReferenceFieldUpdater<Node, Node>
             nextUpdater =
             AtomicReferenceFieldUpdater.newUpdater
             (Node. class , Node. class , "next" );
         private  static  final
             AtomicReferenceFieldUpdater<Node, Object>
             itemUpdater =
             AtomicReferenceFieldUpdater.newUpdater
             (Node. class , Object. class , "item" );
 
         Node(E x) { item = x; }
 
         Node(E x, Node<E> n) { item = x; next = n; }
 
         E getItem() {
             return  item;
         }
 
         boolean  casItem(E cmp, E val) {
             return  itemUpdater.compareAndSet( this , cmp, val);
         }
 
         void  setItem(E val) {
             itemUpdater.set( this , val);
         }
 
         Node<E> getNext() {
             return  next;
         }
 
         boolean  casNext(Node<E> cmp, Node<E> val) {
             return  nextUpdater.compareAndSet( this , cmp, val);
         }
 
         void  setNext(Node<E> val) {
             nextUpdater.set( this , val);
         }
 
     }
 
     private  static  final
         AtomicReferenceFieldUpdater<ConcurrentLinkedQueue, Node>
         tailUpdater =
         AtomicReferenceFieldUpdater.newUpdater
         (ConcurrentLinkedQueue. class , Node. class , "tail" );
     private  static  final
         AtomicReferenceFieldUpdater<ConcurrentLinkedQueue, Node>
         headUpdater =
         AtomicReferenceFieldUpdater.newUpdater
         (ConcurrentLinkedQueue. class ,  Node. class , "head" );
 
     private  boolean  casTail(Node<E> cmp, Node<E> val) {
         return  tailUpdater.compareAndSet( this , cmp, val);
     }
 
     private  boolean  casHead(Node<E> cmp, Node<E> val) {
         return  headUpdater.compareAndSet( this , cmp, val);
     }
 
 
     /**
      * Pointer to header node, initialized to a dummy node.  The first
      * actual node is at head.getNext().
      */
     private  transient  volatile  Node<E> head = new  Node<E>( null , null );
 
     /** Pointer to last node on list **/
     private  transient  volatile  Node<E> tail = head;
 
 
     /**
      * Creates a <tt>ConcurrentLinkedQueue</tt> that is initially empty.
      */
     public  ConcurrentLinkedQueue() {}
 
     /**
      * Creates a <tt>ConcurrentLinkedQueue</tt>
      * initially containing the elements of the given collection,
      * added in traversal order of the collection's iterator.
      * @param c the collection of elements to initially contain
      * @throws NullPointerException if the specified collection or any
      *         of its elements are null
      */
     public  ConcurrentLinkedQueue(Collection<? extends  E> c) {
         for  (Iterator<? extends  E> it = c.iterator(); it.hasNext();)
             add(it.next());
     }
 
     // Have to override just to update the javadoc
 
     /**
      * Inserts the specified element at the tail of this queue.
      *
      * @return <tt>true</tt> (as specified by {@link Collection#add})
      * @throws NullPointerException if the specified element is null
      */
     public  boolean  add(E e) {
         return  offer(e);
     }
 
     /**
      * Inserts the specified element at the tail of this queue.
      *
      * @return <tt>true</tt> (as specified by {@link Queue#offer})
      * @throws NullPointerException if the specified element is null
      */
     public  boolean  offer(E e) {
         if  (e == null ) throw  new  NullPointerException();
         Node<E> n = new  Node<E>(e, null );
         for  (;;) {
             Node<E> t = tail;
             Node<E> s = t.getNext();
             if  (t == tail) {
                 if  (s == null ) {
                     if  (t.casNext(s, n)) {
                         casTail(t, n);
                         return  true ;
                     }
                 } else  {
                     casTail(t, s);
                 }
             }
         }
     }
 
     public  E poll() {
         for  (;;) {
             Node<E> h = head;
             Node<E> t = tail;
             Node<E> first = h.getNext();
             if  (h == head) {
                 if  (h == t) {
                     if  (first == null )
                         return  null ;
                     else
                         casTail(t, first);
                 } else  if  (casHead(h, first)) {
                     E item = first.getItem();
                     if  (item != null ) {
                         first.setItem( null );
                         return  item;
                     }
                     // else skip over deleted item, continue loop,
                 }
             }
         }
     }
 
     public  E peek() { // same as poll except don't remove item
         for  (;;) {
             Node<E> h = head;
             Node<E> t = tail;
             Node<E> first = h.getNext();
             if  (h == head) {
                 if  (h == t) {
                     if  (first == null )
                         return  null ;
                     else
                         casTail(t, first);
                 } else  {
                     E item = first.getItem();
                     if  (item != null )
                         return  item;
                     else  // remove deleted node and continue
                         casHead(h, first);
                 }
             }
         }
     }
 
     /**
      * Returns the first actual (non-header) node on list.  This is yet
      * another variant of poll/peek; here returning out the first
      * node, not element (so we cannot collapse with peek() without
      * introducing race.)
      */
     Node<E> first() {
         for  (;;) {
             Node<E> h = head;
             Node<E> t = tail;
             Node<E> first = h.getNext();
             if  (h == head) {
                 if  (h == t) {
                     if  (first == null )
                         return  null ;
                     else
                         casTail(t, first);
                 } else  {
                     if  (first.getItem() != null )
                         return  first;
                     else  // remove deleted node and continue
                         casHead(h, first);
                 }
             }
         }
     }
 
 
     /**
      * Returns <tt>true</tt> if this queue contains no elements.
      *
      * @return <tt>true</tt> if this queue contains no elements
      */
     public  boolean  isEmpty() {
         return  first() == null ;
     }
 
     /**
      * Returns the number of elements in this queue.  If this queue
      * contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
      * <tt>Integer.MAX_VALUE</tt>.
      *
      * <p>Beware that, unlike in most collections, this method is
      * <em>NOT</em> a constant-time operation. Because of the
      * asynchronous nature of these queues, determining the current
      * number of elements requires an O(n) traversal.
      *
      * @return the number of elements in this queue
      */
     public  int  size() {
         int  count = 0 ;
         for  (Node<E> p = first(); p != null ; p = p.getNext()) {
             if  (p.getItem() != null ) {
                 // Collections.size() spec says to max out
                 if  (++count == Integer.MAX_VALUE)
                     break ;
             }
         }
         return  count;
     }
 
     /**
      * Returns <tt>true</tt> if this queue contains the specified element.
      * More formally, returns <tt>true</tt> if and only if this queue contains
      * at least one element <tt>e</tt> such that <tt>o.equals(e)</tt>.
      *
      * @param o object to be checked for containment in this queue
      * @return <tt>true</tt> if this queue contains the specified element
      */
     public  boolean  contains(Object o) {
         if  (o == null ) return  false ;
         for  (Node<E> p = first(); p != null ; p = p.getNext()) {
             E item = p.getItem();
             if  (item != null  &&
                 o.equals(item))
                 return  true ;
         }
         return  false ;
     }
 
     /**
      * Removes a single instance of the specified element from this queue,
      * if it is present.  More formally, removes an element <tt>e</tt> such
      * that <tt>o.equals(e)</tt>, if this queue contains one or more such
      * elements.
      * Returns <tt>true</tt> if this queue contained the specified element
      * (or equivalently, if this queue changed as a result of the call).
      *
      * @param o element to be removed from this queue, if present
      * @return <tt>true</tt> if this queue changed as a result of the call
      */
     public  boolean  remove(Object o) {
         if  (o == null ) return  false ;
         for  (Node<E> p = first(); p != null ; p = p.getNext()) {
             E item = p.getItem();
             if  (item != null  &&
                 o.equals(item) &&
                 p.casItem(item, null ))
                 return  true ;
         }
         return  false ;
     }
 
     /**
      * Returns an array containing all of the elements in this queue, in
      * proper sequence.
      *
      * <p>The returned array will be "safe" in that no references to it are
      * maintained by this queue.  (In other words, this method must allocate
      * a new array).  The caller is thus free to modify the returned array.
      *
      * <p>This method acts as bridge between array-based and collection-based
      * APIs.
      *
      * @return an array containing all of the elements in this queue
      */
     public  Object[] toArray() {
         // Use ArrayList to deal with resizing.
         ArrayList<E> al = new  ArrayList<E>();
         for  (Node<E> p = first(); p != null ; p = p.getNext()) {
             E item = p.getItem();
             if  (item != null )
                 al.add(item);
         }
         return  al.toArray();
     }
 
     /**
      * Returns an array containing all of the elements in this queue, in
      * proper sequence; the runtime type of the returned array is that of
      * the specified array.  If the queue fits in the specified array, it
      * is returned therein.  Otherwise, a new array is allocated with the
      * runtime type of the specified array and the size of this queue.
      *
      * <p>If this queue fits in the specified array with room to spare
      * (i.e., the array has more elements than this queue), the element in
      * the array immediately following the end of the queue is set to
      * <tt>null</tt>.
      *
      * <p>Like the {@link #toArray()} method, this method acts as bridge between
      * array-based and collection-based APIs.  Further, this method allows
      * precise control over the runtime type of the output array, and may,
      * under certain circumstances, be used to save allocation costs.
      *
      * <p>Suppose <tt>x</tt> is a queue known to contain only strings.
      * The following code can be used to dump the queue into a newly
      * allocated array of <tt>String</tt>:
      *
      * <pre>
      *     String[] y = x.toArray(new String[0]);</pre>
      *
      * Note that <tt>toArray(new Object[0])</tt> is identical in function to
      * <tt>toArray()</tt>.
      *
      * @param a the array into which the elements of the queue are to
      *          be stored, if it is big enough; otherwise, a new array of the
      *          same runtime type is allocated for this purpose
      * @return an array containing all of the elements in this queue
      * @throws ArrayStoreException if the runtime type of the specified array
      *         is not a supertype of the runtime type of every element in
      *         this queue
      * @throws NullPointerException if the specified array is null
      */
     public  <T> T[] toArray(T[] a) {
         // try to use sent-in array
         int  k = 0 ;
         Node<E> p;
         for  (p = first(); p != null  && k < a.length; p = p.getNext()) {
             E item = p.getItem();
             if  (item != null )
                 a[k++] = (T)item;
         }
         if  (p == null ) {
             if  (k < a.length)
                 a[k] = null ;
             return  a;
         }
 
         // If won't fit, use ArrayList version
         ArrayList<E> al = new  ArrayList<E>();
         for  (Node<E> q = first(); q != null ; q = q.getNext()) {
             E item = q.getItem();
             if  (item != null )
                 al.add(item);
         }
         return  al.toArray(a);
     }
 
     /**
      * Returns an iterator over the elements in this queue in proper sequence.
      * The returned iterator is a "weakly consistent" iterator that
      * will never throw {@link ConcurrentModificationException},
      * and guarantees to traverse elements as they existed upon
      * construction of the iterator, and may (but is not guaranteed to)
      * reflect any modifications subsequent to construction.
      *
      * @return an iterator over the elements in this queue in proper sequence
      */
     public  Iterator<E> iterator() {
         return  new  Itr();
     }
 
     private  class  Itr implements  Iterator<E> {
         /**
          * Next node to return item for.
          */
         private  Node<E> nextNode;
 
         /**
          * nextItem holds on to item fields because once we claim
          * that an element exists in hasNext(), we must return it in
          * the following next() call even if it was in the process of
          * being removed when hasNext() was called.
          */
         private  E nextItem;
 
         /**
          * Node of the last returned item, to support remove.
          */
         private  Node<E> lastRet;
 
         Itr() {
             advance();
         }
 
         /**
          * Moves to next valid node and returns item to return for
          * next(), or null if no such.
          */
         private  E advance() {
             lastRet = nextNode;
             E x = nextItem;
 
             Node<E> p = (nextNode == null )? first() : nextNode.getNext();
             for  (;;) {
                 if  (p == null ) {
                     nextNode = null ;
                     nextItem = null ;
                     return  x;
                 }
                 E item = p.getItem();
                 if  (item != null ) {
                     nextNode = p;
                     nextItem = item;
                     return  x;
                 } else  // skip over nulls
                     p = p.getNext();
             }
         }
 
         public  boolean  hasNext() {
             return  nextNode != null ;
         }
 
         public  E next() {
             if  (nextNode == null ) throw  new  NoSuchElementException();
             return  advance();
         }
 
         public  void  remove() {
             Node<E> l = lastRet;
             if  (l == null ) throw  new  IllegalStateException();
             // rely on a future traversal to relink.
             l.setItem( null );
             lastRet = null ;
         }
     }
 
     /**
      * Save the state to a stream (that is, serialize it).
      *
      * @serialData All of the elements (each an <tt>E</tt>) in
      * the proper order, followed by a null
      * @param s the stream
      */
     private  void  writeObject(java.io.ObjectOutputStream s)
         throws  java.io.IOException {
 
         // Write out any hidden stuff
         s.defaultWriteObject();
 
         // Write out all elements in the proper order.
         for  (Node<E> p = first(); p != null ; p = p.getNext()) {
             Object item = p.getItem();
             if  (item != null )
                 s.writeObject(item);
         }
 
         // Use trailing null as sentinel
         s.writeObject( null );
     }
 
     /**
      * Reconstitute the Queue instance from a stream (that is,
      * deserialize it).
      * @param s the stream
      */
     private  void  readObject(java.io.ObjectInputStream s)
         throws  java.io.IOException, ClassNotFoundException {
         // Read in capacity, and any hidden stuff
         s.defaultReadObject();
         head = new  Node<E>( null , null );
         tail = head;
         // Read in all elements and place in queue
         for  (;;) {
             E item = (E)s.readObject();
             if  (item == null )
                 break ;
             else
                 offer(item);
         }
     }
 
}
高并发环境下优化锁或无锁(lock-free)的设计思路

服务端编程的3大性能杀手:1、大量线程导致的线程切换开销。2、锁。3、非必要的内存拷贝。在高并发下,对于纯内存操作来说,单线程是要比多线程快的, 可以比较一下多线程程序在压力测试下cpu的sy和ni百分比。高并发环境下要实现高吞吐量和线程安全,两个思路:一个是用优化的锁实现,一个是lock-free的无锁结构。但非阻塞算法要比基于锁的算法复杂得多。开发非阻塞算法是相当专业的训练,而且要证明算法的正确也极为困难,不仅和具体的目标机器平台和编译器相关,而且需要复杂的技巧和严格的测试。虽然Lock-Free编程非常困难,但是它通常可以带来比基于锁编程更高的吞吐量。所以Lock-Free编程是大有前途的技术。它在线程中止、优先级倒置以及信号安全等方面都有着良好的表现。

  • 优化锁实现的例子:Java中的ConcurrentHashMap,设计巧妙,用桶粒度的锁和锁分离机制,避免了put和get中对整个map的锁定,尤其在get中,只对一个HashEntry做锁定操作,性能提升是显而易见的(详细分析见《探索 ConcurrentHashMap 高并发性的实现机制》)。
  • Lock-free无锁的例子:CAS(CPU的Compare-And-Swap指令)的利用和LMAX的disruptor无锁消息队列数据结构等。有兴趣了解LMAX的disruptor无锁消息队列数据结构的可以移步slideshare

disruptor无锁消息队列数据结构的类图和技术文档下载

2014-02-12_16h55_36

另外,在设计思路上除了尽量减少资源争用以外,还可以借鉴nginx/node.js等单线程大循环的机制,用单线程或CPU数相同的线程开辟大的队列,并发的时候任务压入队列,线程轮询然后一个个顺序执行。由于每个都采用异步I/O,没有阻塞线程。这个大队列可以使用RabbitMQueue,或是JDK的同步队列(性能稍差),或是使用Disruptor无锁队列(Java)。任务处理可以全部放在内存(多级缓存、读写分离、ConcurrentHashMap、甚至分布式缓存Redis)中进行增删改查。最后用Quarz维护定时把缓存数据同步到DB中。当然,这只是中小型系统的思路,如果是大型分布式系统会非常复杂,需要分而治理,用SOA的思路,参考这篇文章的图。(注:Redis是单线程的纯内存数据库,单线程无需锁,而Memcache是多线程的带CAS算法,两者都使用epoll,no-blocking io)

png;base643f17317a5d7e7fe9

深入JVM的OS的无锁非阻塞算法

如果深入 JVM 和操作系统,会发现非阻塞算法无处不在。垃圾收集器使用非阻塞算法加快并发和平行的垃圾搜集;调度器使用非阻塞算法有效地调度线程和进程,实现内在锁。在 Mustang(Java 6.0)中,基于锁的 SynchronousQueue 算法被新的非阻塞版本代替。很少有开发人员会直接使用 SynchronousQueue,但是通过 Executors.newCachedThreadPool() 工厂构建的线程池用它作为工作队列。比较缓存线程池性能的对比测试显示,新的非阻塞同步队列实现提供了几乎是当前实现 3 倍的速度。在 Mustang 的后续版本(代码名称为 Dolphin)中,已经规划了进一步的改进。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值