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

简介: 一、CAS和synchronized适用场景 1、对于资源竞争较少的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而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。

http://www.cnblogs.com/everSeeker/p/5569414.html

 

相关文章
|
16天前
|
SQL Java 数据库
2025 年 Java 从零基础小白到编程高手的详细学习路线攻略
2025年Java学习路线涵盖基础语法、面向对象、数据库、JavaWeb、Spring全家桶、分布式、云原生与高并发技术,结合实战项目与源码分析,助力零基础学员系统掌握Java开发技能,从入门到精通,全面提升竞争力,顺利进阶编程高手。
212 1
|
16天前
|
Java 开发者
Java并发编程:CountDownLatch实战解析
Java并发编程:CountDownLatch实战解析
307 100
|
27天前
|
NoSQL Java 关系型数据库
超全 Java 学习路线,帮你系统掌握编程的超详细 Java 学习路线
本文为超全Java学习路线,涵盖基础语法、面向对象编程、数据结构与算法、多线程、JVM原理、主流框架(如Spring Boot)、数据库(MySQL、Redis)及项目实战等内容,助力从零基础到企业级开发高手的进阶之路。
132 1
|
1月前
|
算法 Java
Java多线程编程:实现线程间数据共享机制
以上就是Java中几种主要处理多线程序列化资源以及协调各自独立运行但需相互配合以完成任务threads 的技术手段与策略。正确应用上述技术将大大增强你程序稳定性与效率同时也降低bug出现率因此深刻理解每项技术背后理论至关重要.
90 16
|
2月前
|
安全 Java Shell
Java模块化编程(JPMS)简介与实践
本文全面解析Java 9模块化系统(JPMS),帮助开发者解决JAR地狱、类路径冲突等常见问题,提升代码的封装性、性能与可维护性。内容涵盖模块化核心概念、module-info语法、模块声明、实战迁移、多模块项目构建、高级特性及最佳实践,同时提供常见问题和面试高频题解析,助你掌握Java模块化编程精髓,打造更健壮的应用。
|
2月前
|
安全 算法 Java
Java泛型编程:类型安全与擦除机制
Java泛型详解:从基础语法到类型擦除机制,深入解析通配符与PECS原则,探讨运行时类型获取技巧及最佳实践,助你掌握泛型精髓,写出更安全、灵活的代码。
|
2月前
|
安全 Java 数据库连接
2025 年最新 Java 学习路线图含实操指南助你高效入门 Java 编程掌握核心技能
2025年最新Java学习路线图,涵盖基础环境搭建、核心特性(如密封类、虚拟线程)、模块化开发、响应式编程、主流框架(Spring Boot 3、Spring Security 6)、数据库操作(JPA + Hibernate 6)及微服务实战,助你掌握企业级开发技能。
281 3
|
2月前
|
Java
Java编程:理解while循环的使用
总结而言, 使用 while 迴圈可以有效解决需要多次重复操作直至特定條件被触发才停止執行任务场景下问题; 它简单、灵活、易于实现各种逻辑控制需求但同时也要注意防止因邏各错误导致無限迁璇発生及及時處理可能発生异常以确保程序稳定运作。
206 0
|
2月前
|
安全 Cloud Native Java
Java:历久弥新的企业级编程基石
Java:历久弥新的企业级编程基石
|
2月前
|
移动开发 Cloud Native Java
Java:历久弥新的企业级编程基石
Java:历久弥新的企业级编程基石