转载:http://www.jb51.net/article/86192.htm

下面小编就为大家带来一篇Java并发编程总结——慎用CAS详解。小编觉得挺不错的, 现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧

一、CAS和synchronized适用场景

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

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

1
2
3
4
5
6
7
8
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
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
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行

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

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

三、改进的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
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。

四、总结

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

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

以上这篇Java并发编程总结——慎用CAS详解就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持脚本之家。

CAS适用场景的更多相关文章

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

    一.CAS和synchronized适用场景 1.对于资源竞争较少的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源:而CAS基于硬件实 ...

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

    一.CAS和synchronized适用场景 1.对于资源竞争较少的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源:而CAS基于硬件实 ...

  3. Java高性能编程之CAS与ABA及解决方法

    Java高性能编程之CAS与ABA及解决方法 前言 如果喜欢暗色调的界面或者想换换界面,可以看看我在个人博客发布的 Java高性能编程之CAS与ABA及解决方法. CAS概念 CAS,全称Compar ...

  4. CAS你知道吗?原子类AtomicInteger的ABA问题谈谈?

    (1)CAS是什么?  比较并交换 举例1,  CAS产生场景代码? import java.util.concurrent.atomic.AtomicInteger; public class CA ...

  5. 并发编程的基石——CAS机制

    本博客系列是学习并发编程过程中的记录总结.由于文章比较多,写的时间也比较散,所以我整理了个目录贴(传送门),方便查阅. 并发编程系列博客传送门 Java中提供了很多原子操作类来保证共享变量操作的原子性 ...

  6. disruptor 高并发编程 简介demo

    原文地址:http://www.cnblogs.com/qiaoyihang/p/6479994.html disruptor适用于大规模低延迟的并发场景.可用于读写操作分离.数据缓存,速度匹配(因为 ...

  7. 2019滴滴java面试总结 (包含面试题解析)

    2019滴滴java面试总结  (包含面试题) 本人6年开发经验.今年年初找工作,在互联网寒冬下成功拿到阿里巴巴.今日头条.滴滴等公司offer,岗位是既有php也有Java后端开发,最终选择去了滴滴 ...

  8. 2019头条java面试总结 (包含面试题解析)

    2019滴滴java面试总结  (包含面试题) 本人8年开发经验.今年年初找工作,在互联网寒冬下成功拿到阿里巴巴.今日头条.滴滴等公司offer,岗位是Java后端开发. 面试了很多家公司,感觉大部分 ...

  9. Java并发-Synchronized关键字

    一.多线程下的i++操作的并发问题 package passtra; public class SynchronizedDemo implements Runnable{ private static ...

随机推荐

  1. PATH环境变量

    PATH是环境变量,要大写 那几个目录是你放置linux命令的目录,输入命令后系统会去PATH中寻找是否存在该命令 查看当前环境变量: echo $PATH 也可以用set命令看一下 设置: expo ...

  2. C#设计模式之十八状态模式(State Pattern)【行为型】

    一.引言 今天我们开始讲“行为型”设计模式的第六个模式,该模式是[状态模式],英文名称是:State Pattern.无论是现实世界,还是面向对象的OO世界,里面都有一个东西,那就是对象.有对象当然就 ...

  3. MyBatis中映射器Mapper概述

    MyBatis真正强大之处在于它的映射器.因为它异常强大并且编写相对简单,不仅比传统编写SQL语句做的更好并且能节省将近95%的代码量 XML中顶级元素汇总 cache: 给定命名空间的缓存配置 ca ...

  4. ES6核心特性

    摘要:聊JS离不开ES6啊! 原文:ES6核心特性 作者:ljianshu 前言 ES6 虽提供了许多新特性,但我们实际工作中用到频率较高并不多,根据二八法则,我们应该用百分之八十的精力和时间,好好专 ...

  5. 使用mybatis开发dao问题总结

    代码片段: @Override public User getUserById(Integer id) { SqlSession sqlSession = sqlSessionFactory.open ...

  6. 现在有两个变量,分别是a = 3, b = 4,那么我们不用第三个变量来调换a和b的值。

    现在有两个变量,分别是a = 3, b = 4,那么我们不用第三个变量来调换a和b的值. <!DOCTYPE html><html><head>    <me ...

  7. BZOJ4804: 欧拉心算(莫比乌斯反演 线性筛)

    题意 求$$\sum_1^n \sum_1^n \phi(gcd(i, j))$$ $T \leqslant 5000, N \leqslant 10^7$ Sol 延用BZOJ4407的做法 化到最 ...

  8. 生理周期POJ 1006

    Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 138101   Accepted: 44225 Description 人生 ...

  9. How To Do Master Record Mass Maintenance

    How To Do Master Record Mass Maintenance Which master records mass maintenance can be done? What do ...

  10. ViewPager结合view无限滑动

    使用viewPager进无限滑动,这里的实现是在适配器里面进行,当然在外头使用滑动监听也行. import android.support.v4.view.PagerAdapter; import a ...