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错误的使用场景
 public class CASDemo {
     private final int THREAD_NUM = 1000;
     private final int MAX_VALUE = 20000000;
     private AtomicInteger casI = new AtomicInteger(0);
     private int syncI = 0;
     private String path = "/Users/pingping/DataCenter/Books/Linux/Linux常用命令详解.txt";
     public void casAdd() throws InterruptedException {
         long begin = System.currentTimeMillis();
         Thread[] threads = new Thread[THREAD_NUM];
         for (int i = 0; i < THREAD_NUM; i++) {
             threads[i] = new Thread(new Runnable() {
                 public void run() {
                     while (casI.get() < MAX_VALUE) {
                         casI.getAndIncrement();
                     }
                 }
             });
             threads[i].start();
         }
         for (int j = 0; j < THREAD_NUM; j++) {
             threads[j].join();
         }
         System.out.println("CAS costs time: " + (System.currentTimeMillis() - begin));
     }
     public void syncAdd() throws InterruptedException {
         long begin = System.currentTimeMillis();
         Thread[] threads = new Thread[THREAD_NUM];
         for (int i = 0; i < THREAD_NUM; i++) {
             threads[i] = new Thread(new Runnable() {
                 public void run() {
                     while (syncI < MAX_VALUE) {
                         synchronized ("syncI") {
                             ++syncI;
                         }
                     }
                 }
             });
             threads[i].start();
         }
         for (int j = 0; j < THREAD_NUM; j++)
             threads[j].join();
         System.out.println("sync costs time: " + (System.currentTimeMillis() - begin));
     }
 }
在我的双核cpu上运行,结果如下:

可见在不同的线程下,采用CAS计算消耗的时间远多于使用synchronized方式。原因在于第15行
14                     while (casI.get() < MAX_VALUE) {
15                         casI.getAndIncrement();
16                     }
的操作是一个耗时非常少的操作,15行执行完之后会立刻进入循环,继续执行,从而导致线程冲突严重。
三、改进的CAS使用场景
为了解决上述问题,只需要让每一次循环执行的时间变长,即可以大幅减少线程冲突。修改代码如下:
 public class CASDemo {
     private final int THREAD_NUM = 1000;
     private final int MAX_VALUE = 1000;
     private AtomicInteger casI = new AtomicInteger(0);
     private int syncI = 0;
     private String path = "/Users/pingping/DataCenter/Books/Linux/Linux常用命令详解.txt";
     public void casAdd2() throws InterruptedException {
         long begin = System.currentTimeMillis();
         Thread[] threads = new Thread[THREAD_NUM];
         for (int i = 0; i < THREAD_NUM; i++) {
             threads[i] = new Thread(new Runnable() {
                 public void run() {
                     while (casI.get() < MAX_VALUE) {
                         casI.getAndIncrement();
                         try (InputStream in = new FileInputStream(new File(path))) {
                                 while (in.read() != -1);
                         } catch (IOException e) {
                             e.printStackTrace();
                         }
                     }
                 }
             });
             threads[i].start();
         }
         for (int j = 0; j < THREAD_NUM; j++)
             threads[j].join();
         System.out.println("CAS Random costs time: " + (System.currentTimeMillis() - begin));
     }
     public void syncAdd2() throws InterruptedException {
         long begin = System.currentTimeMillis();
         Thread[] threads = new Thread[THREAD_NUM];
         for (int i = 0; i < THREAD_NUM; i++) {
             threads[i] = new Thread(new Runnable() {
                 public void run() {
                     while (syncI < MAX_VALUE) {
                         synchronized ("syncI") {
                             ++syncI;
                         }
                         try (InputStream in = new FileInputStream(new File(path))) {
                             while (in.read() != -1);
                         } catch (IOException e) {
                             e.printStackTrace();
                         }
                     }
                 }
             });
             threads[i].start();
         }
         for (int j = 0; j < THREAD_NUM; j++)
             threads[j].join();
         System.out.println("sync costs time: " + (System.currentTimeMillis() - begin));
     }
 }
在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。
Java并发编程总结2——慎用CAS的更多相关文章
- Java并发编程总结2——慎用CAS(转)
		
一.CAS和synchronized适用场景 1.对于资源竞争较少的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源:而CAS基于硬件实 ...
 - Java并发编程:什么是CAS?这回总算知道了
		
无锁的思想 众所周知,Java中对并发控制的最常见方法就是锁,锁能保证同一时刻只能有一个线程访问临界区的资源,从而实现线程安全.然而,锁虽然有效,但采用的是一种悲观的策略.它假设每一次对临界区资源的访 ...
 - Java并发编程系列-(3) 原子操作与CAS
		
3. 原子操作与CAS 3.1 原子操作 所谓原子操作是指不会被线程调度机制打断的操作:这种操作一旦开始,就一直运行到结束,中间不会有任何context switch,也就是切换到另一个线程. 为了实 ...
 - JAVA并发编程: CAS和AQS
		
版权声明:本文为博主原创文章,转载请注明出处 https://blog.csdn.net/u010862794/article/details/72892300 说起JAVA并发编程,就不得不聊 ...
 - Java并发编程入门与高并发面试(三):线程安全性-原子性-CAS(CAS的ABA问题)
		
摘要:本文介绍线程的安全性,原子性,java.lang.Number包下的类与CAS操作,synchronized锁,和原子性操作各方法间的对比. 线程安全性 线程安全? 线程安全性? 原子性 Ato ...
 - 【Java并发编程实战】----- AQS(四):CLH同步队列
		
在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形.其主要从两方面进行了改造:节点的结构与节点等待机制.在结构上引入了头 ...
 - 【Java并发编程实战】-----“J.U.C”:ReentrantReadWriteLock
		
ReentrantLock实现了标准的互斥操作,也就是说在某一时刻只有有一个线程持有锁.ReentrantLock采用这种独占的保守锁直接,在一定程度上减低了吞吐量.在这种情况下任何的"读/ ...
 - Java并发编程:volatile关键字解析
		
Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在 ...
 - JAVA并发编程J.U.C学习总结
		
前言 学习了一段时间J.U.C,打算做个小结,个人感觉总结还是非常重要,要不然总感觉知识点零零散散的. 有错误也欢迎指正,大家共同进步: 另外,转载请注明链接,写篇文章不容易啊,http://www. ...
 
随机推荐
- NOIP水题测试(2017082501)
			
日常水题测试又来了! 以后答案都以单题形式公布. 下面看今天的水题: 时间限制:5小时 题目一:无法形容的水 题目二:比上一题还水 题目三:一元三次方程求解 题目四:单词接龙 题目五:统计单词个数 题 ...
 - Java利用递归实现扫雷
			
package 扫雷; import java.math.*; import java.util.Scanner; public class 扫雷 { //记录翻开次数 static int k=0; ...
 - hdu6069 多校Counting Divisors
			
思路:对于n^k其实就是每个因子的个数乘了一个K.然后现在就变成了求每个数的每个质因子有多少个,但是比赛的时候只想到sqrt(n)的分解方法,总复杂度爆炸,就一直没过去,然后赛后看官方题解感觉好妙啊! ...
 - 使用ServiceDesk Plus保证及时解决问题,防止违反SLA
 - dblink(转)
			
oracle在进行跨库访问时,可以通过创建dblink实现,今天就简单的介绍下如果创建dblink,以及通过dblink完成插入.修改.删除等操作 首先了解下环境:在tnsnames.ora中配置两个 ...
 - SHELL脚本取系统当前年月日问题 (去0)
			
1. #!/bin/bash tmonth=`date +%m`tyear=`date +%y`tday=`date +%d`day=`expr $tday + 0`month=`expr $tmon ...
 - 上传文件夹+php
			
最近公司做工程项目,实现文件夹上传 网上找了很久,发现网上很多代码大都存在很多问题,不过还是让我找到了一个符合要求的项目. 对项目的文件夹上传功能做出分析,找出文件夹上传的原理,对文件夹的传输模式深入 ...
 - C#程序集问题:混合模式程序集是针对“v2.0.50727”版的运行时生成的.....
			
今天在把以前写的代码生成工具从原来的.NET3.5升级到.NET4.0,同时准备进一步完善,将程序集都更新后,一运行程序在一处方法调用时报出了一个异常: 混合模式程序集是针对“v2.0.50727”版 ...
 - 【JS库】URI.js
			
做前端的,应该有不少人都写过操作URL的代码,比如提取问号后面的参数.或者主机名什么的,比如这样: var url="http://jszai.com/foo?bar=baz", ...
 - window下切换python
			
自己的win10装了2.7和3.6版本的python.本不想装2.7的,但node.js的C++的编译居然用到2.X的python,没法子就装了2.7.那怎么切换呢? 为了方便使用,我在系统的path ...