Java并发编程总结2——慎用CAS

一、CAS和synchronized适用场景

1、对于资源竞争较少的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。

2、对于资源竞争严重的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。以java.util.concurrent.atomic包中AtomicInteger类为例,其getAndIncrement()方法实现如下:

public final int getAndIncrement() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return current;
        }
}

如果compareAndSet(current, next)方法成功执行,则直接返回;如果线程竞争激烈,导致compareAndSet(current, next)方法一直不能成功执行,则会一直循环等待,直到耗尽cpu分配给该线程的时间片,从而大幅降低效率。

二、CAS错误的使用场景

 1 public class CASDemo {
 2     private final int THREAD_NUM = 1000;
 3     private final int MAX_VALUE = 20000000;
 4     private AtomicInteger casI = new AtomicInteger(0);
 5     private int syncI = 0;
 6     private String path = "/Users/pingping/DataCenter/Books/Linux/Linux常用命令详解.txt";
 7
 8     public void casAdd() throws InterruptedException {
 9         long begin = System.currentTimeMillis();
10         Thread[] threads = new Thread[THREAD_NUM];
11         for (int i = 0; i < THREAD_NUM; i++) {
12             threads[i] = new Thread(new Runnable() {
13                 public void run() {
14                     while (casI.get() < MAX_VALUE) {
15                         casI.getAndIncrement();
16                     }
17                 }
18             });
19             threads[i].start();
20         }
21         for (int j = 0; j < THREAD_NUM; j++) {
22             threads[j].join();
23         }
24         System.out.println("CAS costs time: " + (System.currentTimeMillis() - begin));
25     }
26
27     public void syncAdd() throws InterruptedException {
28         long begin = System.currentTimeMillis();
29         Thread[] threads = new Thread[THREAD_NUM];
30         for (int i = 0; i < THREAD_NUM; i++) {
31             threads[i] = new Thread(new Runnable() {
32                 public void run() {
33                     while (syncI < MAX_VALUE) {
34                         synchronized ("syncI") {
35                             ++syncI;
36                         }
37                     }
38                 }
39             });
40             threads[i].start();
41         }
42         for (int j = 0; j < THREAD_NUM; j++)
43             threads[j].join();
44         System.out.println("sync costs time: " + (System.currentTimeMillis() - begin));
45     }
46 }

在我的双核cpu上运行,结果如下:

可见在不同的线程下,采用CAS计算消耗的时间远多于使用synchronized方式。原因在于第15行

14                     while (casI.get() < MAX_VALUE) {
15                         casI.getAndIncrement();
16                     }

的操作是一个耗时非常少的操作,15行执行完之后会立刻进入循环,继续执行,从而导致线程冲突严重。

三、改进的CAS使用场景

为了解决上述问题,只需要让每一次循环执行的时间变长,即可以大幅减少线程冲突。修改代码如下:

 1 public class CASDemo {
 2     private final int THREAD_NUM = 1000;
 3     private final int MAX_VALUE = 1000;
 4     private AtomicInteger casI = new AtomicInteger(0);
 5     private int syncI = 0;
 6     private String path = "/Users/pingping/DataCenter/Books/Linux/Linux常用命令详解.txt";
 7
 8     public void casAdd2() throws InterruptedException {
 9         long begin = System.currentTimeMillis();
10         Thread[] threads = new Thread[THREAD_NUM];
11         for (int i = 0; i < THREAD_NUM; i++) {
12             threads[i] = new Thread(new Runnable() {
13                 public void run() {
14                     while (casI.get() < MAX_VALUE) {
15                         casI.getAndIncrement();
16                         try (InputStream in = new FileInputStream(new File(path))) {
17                                 while (in.read() != -1);
18                         } catch (IOException e) {
19                             e.printStackTrace();
20                         }
21                     }
22                 }
23             });
24             threads[i].start();
25         }
26         for (int j = 0; j < THREAD_NUM; j++)
27             threads[j].join();
28         System.out.println("CAS Random costs time: " + (System.currentTimeMillis() - begin));
29     }
30
31     public void syncAdd2() throws InterruptedException {
32         long begin = System.currentTimeMillis();
33         Thread[] threads = new Thread[THREAD_NUM];
34         for (int i = 0; i < THREAD_NUM; i++) {
35             threads[i] = new Thread(new Runnable() {
36                 public void run() {
37                     while (syncI < MAX_VALUE) {
38                         synchronized ("syncI") {
39                             ++syncI;
40                         }
41                         try (InputStream in = new FileInputStream(new File(path))) {
42                             while (in.read() != -1);
43                         } catch (IOException e) {
44                             e.printStackTrace();
45                         }
46                     }
47                 }
48             });
49             threads[i].start();
50         }
51         for (int j = 0; j < THREAD_NUM; j++)
52             threads[j].join();
53         System.out.println("sync costs time: " + (System.currentTimeMillis() - begin));
54     }
55 }

在while循环中,增加了一个读取文件内容的操作,该操作大概需要耗时40ms,从而可以减少线程冲突。测试结果如下:

可见在资源冲突比较小的情况下,采用CAS方式和synchronized同步效率差不多。为什么CAS相比synchronized没有获得更高的性能呢?

测试使用的jdk为1.7,而从jdk1.6开始,对锁的实现引入了大量的优化,如锁粗化(Lock Coarsening)、锁消除(Lock Elimination)、轻量级锁(Lightweight Locking)、偏向锁(Biased Locking)、适应性自旋(Adaptive Spinning)等技术来减少锁操作的开销。而其中自旋锁的原理,类似于CAS自旋,甚至比CAS自旋更为优化。具体内容请参考 深入JVM锁机制1-synchronized。

传送门:http://blog.csdn.net/chen77716/article/details/6618779

四、总结

1、使用CAS在线程冲突严重时,会大幅降低程序性能;CAS只适合于线程冲突较少的情况使用。

2、synchronized在jdk1.6之后,已经改进优化。synchronized的底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。

时间: 2024-10-13 04:04:44

Java并发编程总结2——慎用CAS的相关文章

Java并发编程总结2——慎用CAS(转)

一.CAS和synchronized适用场景 1.对于资源竞争较少的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源:而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能. 2.对于资源竞争严重的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized.以java.util.concurrent.atomic包中AtomicInteger类为例,其getAn

Java并发编程:什么是CAS?这回总算知道了

无锁的思想 众所周知,Java中对并发控制的最常见方法就是锁,锁能保证同一时刻只能有一个线程访问临界区的资源,从而实现线程安全.然而,锁虽然有效,但采用的是一种悲观的策略.它假设每一次对临界区资源的访问都会发生冲突,当有一个线程访问资源,其他线程就必须等待,所以锁是会阻塞线程执行的. 当然,凡事都有两面,有悲观就会有乐观.而无锁就是一种乐观的策略,它假设线程对资源的访问是没有冲突的,同时所有的线程执行都不需要等待,可以持续执行.如果遇到冲突的话,就使用一种叫做CAS (比较交换) 的技术来鉴别线

基于CAS线程安全的计算方法 java并发编程的艺术上的一个案例

package thread; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; /**  * @author  changxiangxiang  * @date 2014年8月6日 下午3:25:12  * @description  * @since  sprint2  */ public class Counter {     privat

Java并发编程:Concurrent锁机制解析

.title { text-align: center } .todo { font-family: monospace; color: red } .done { color: green } .tag { background-color: #eee; font-family: monospace; padding: 2px; font-size: 80%; font-weight: normal } .timestamp { color: #bebebe } .timestamp-kwd

6、Java并发编程:volatile关键字解析

Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识,然后分析了volatil

读《Java并发编程的艺术》(一)

离开博客园很久了,自从找到工作,到现在基本没有再写过博客了.在大学培养起来的写博客的习惯在慢慢的消失殆尽,感觉汗颜.所以现在要开始重新培养起这个习惯,定期写博客不仅是对自己学习知识的一种沉淀,更是在督促自己要不断的学习,不断的进步. 最近在进一步学习Java并发编程,不言而喻,这部分内容是很重要的.现在就以<并发编程的艺术>一书为主导线,开始新一轮的学习. 进程和线程 进程是一个应用程序在处理机上的一次执行过程,线程是进程的最小基本单位(个人理解).一个进程可以包含多个线程. 上下文切换 我们

Java并发编程学习路线

一年前由于工作需要从微软技术栈入坑Java,并陆陆续续做了一个Java后台项目,目前在搞Scala+Java混合的后台开发,一直觉得并发编程是所有后台工程师的基本功,所以也学习了小一年Java的并发工具,对整体的并发理解乃至分布式都有一定的提高,所以想和大家分享一下. 我的学习路线 首先说说学习路线,我一开始是直接上手JCIP(Java Concurrency in Practice),发现不是很好懂,把握不了那本书的主线,所以思索着从国内的作者开始先,所以便读了下方腾飞的<Java并发编程的艺

JAVA并发编程J.U.C学习总结

前言 学习了一段时间J.U.C,打算做个小结,个人感觉总结还是非常重要,要不然总感觉知识点零零散散的. 有错误也欢迎指正,大家共同进步: 另外,转载请注明链接,写篇文章不容易啊,http://www.cnblogs.com/chenpi/p/5614290.html 本文目录如下,基本上涵盖了J.U.C的主要内容: JSR 166及J.U.C Executor框架(线程池. Callable .Future) AbstractQueuedSynchronizer(AQS框架) Locks & C

Java并发编程总结3——AQS、ReentrantLock、ReentrantReadWriteLock

本文内容主要总结自<Java并发编程的艺术>第5章——Java中的锁. 一.AQS AbstractQueuedSynchronizer(简称AQS),队列同步器,是用来构建锁或者其他同步组建的基础框架.该类主要包括: 1.模式,分为共享和独占. 2.volatile int state,用来表示锁的状态. 3.FIFO双向队列,用来维护等待获取锁的线程. AQS部分代码及说明如下: public abstract class AbstractQueuedSynchronizer extend