专栏首页渔夫Java并发-从JDK源码角度看什么时候使用CAS机制

Java并发-从JDK源码角度看什么时候使用CAS机制

一、引子

 如果我问你在Java语言环境下何时使用CAS机制,你可能会说:出现线程不安全可能性的时候就是我们应当使用CAS机制的时候。但是这个说话虽然是正确的,但是太笼统以至于说了好像没说一样。如果你学过synchronized关键字,你一定知道同步机制带来的内存上的损耗是很大的,比如频繁的上下文切换就是我们在使用synchronized关键字时急需避免的。但是如果你了解CAS机制的话,你就会知道此机制有可能会导致线程占据CPU资源,如果在线程安全的条件下仍然使用CAS机制,那么就会带来不必要的CPU资源损耗。

二、何时使用CAS机制

首先给出使用CAS机制的原则:

  1. 线程之间抢占资源不是特别激烈使用CAS机制,这保证了大部分线程不会是在干等资源的释放
  2. 等待资源释放时的CPU占用反而小于上下文切换所消耗的资源,使用CAS机制
  3. 线程可能出现不安全情况的条件下才使用CAS机制

解释:

  1. CAS机制由于往往和自锁(for(;;))机制相结合使用,所以在自旋机制下,线程竞争越激烈,越多的线程在循环中等待资源释放,而这个过程是占据CPU资源的
  2. 第二点的内涵是:我们需要确保synchronized关键字性能比CAS机制差
  3. 第三点的解释看似平常,但是却是我们平常不关注的地方,以下我们JDK源代码做解释:
final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();//得到访问锁对象的当前线程对象
            int c = getState();//得到当前锁对象的状态
            if (c == 0) {	//状态为0,意味着没有任何线程占据着当前锁对象
                if (compareAndSetState(0, acquires)) {//使用CAS机制将当前锁状态更新,只有一个线程会成功,返回true
                    setExclusiveOwnerThread(current);//将当前线程置为锁的独占线程
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {//如果当前线程卡位占据锁对象的线程
                int nextc = c + acquires;//得到当前线程重入锁后的状态
                if (nextc < 0) // overflow//这是锁状态的非法值,如若此值,则抛出异常
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);//调用set方法,更新状态值。
                return true;
            }
            return false;
        }

 以上代码是java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire中所定义的当前线程尝试获取资源的方法,可能你还没有学过AQS机制,Lock接口,但是通过我上述对代码的注释,相信你应该对这个代码块可以有一个大致的认识。  不知道你有没有注意到一点,上述代码有两处用不同的方法进行锁状态的更新if (compareAndSetState(0, acquires)) 以及setState(nextc);  但是为何目的都是锁对象状态更新,实现方式却是一个CAS机制,一个普通的set方法。 原因是上述原则中的第三点:CAS机制使用处可能出现线程不安全情况,而后者却是一定处于线程安全情况。下面来说说具体的判断原因:

  1. 首先说明上述代码块的锁特性:上述锁结构是一个独占锁,只允许一个线程占据锁资源,但是允许一个线程多次占据锁资源(重入);
  2. 当锁资源没有被任何线程占据,那么可能出现多个线程同时去抢占锁资源的情况,此时线程显然是不安全的,所以需要使用CAS机制来进行线程安全性的保证,并且多个抢占资源的线程中只有一个线程会抢占到所资源,所以将其放置于if逻辑判断语句中,只有成功的线程才会被设置为当前锁对象的独占线程;
  3. 而后者调用普通的set方法原因是:允许重入锁的条件是占据锁资源的线程恰好为当前访问锁对象的线程,这样的线程有且只有一个,那么进行状态更新时,就相当于我们尚未学习多线程知识前单线程的set方法,无须考虑线程不安全性,那么就无须使用CAS机制。

三、小结

 从CAS机制使用原则上我们还是可以看出一点,如果能笃定地根据代码逻辑判断出当前代码块是被单线程访问或者执行的,那么我们应当坚决拥护最简单的单线程中的写方法。不是说在学习好多线程知识之后我们在何时何处都应当使用多线程的写方法来保护线程安全性。如果多线程带来线程安全性保障是不必要的,那么多线程导致的额外损耗就是多余。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java并发-被动使线程wait/notify

     此任务的目标是安全、高效地使一个运行着的线程开始进入wait状态以及从wait状态中唤醒。而此操作都是通过其他线程被动地使当前线程将运行状态的强制转换,具体实...

    Fisherman渔夫
  • Java并发-守护线程-Daemon

     我们如上在main线程中定义了一个子线程t,将子线程的run方法写为sleep调用比main方法的sleep调用更久时间,以验证父类线程main的执行完毕并释...

    Fisherman渔夫
  • 栈和堆的访问速度以及对象创建

     首先回答是不是的问题:对象并非只能存在于内存中的堆,其可以存于栈上。这是因为栈和堆在内存角度上看,没有任何区别。

    Fisherman渔夫
  • SpringBoot开发案例之多任务并行+线程池处理

    前几篇文章着重介绍了后端服务数据库和多线程并行处理优化,并示例了改造前后的伪代码逻辑。当然了,优化是无止境的,前人栽树后人乘凉。作为我们开发者来说,既然站在了巨...

    小柒2012
  • 「每天一道面试题」ReentrantLock是如何实现公平锁及可重入的?

    A、B两个线程同时执行lock()方法获取锁,假设A先执行获取到锁,此时state值加1,如果线程A在继续执行的过程中又执行了lock()方法(根据持有锁的线程...

    JavaQ
  • SpringBoot开发案例之多任务并行+线程池处理

    前几篇文章着重介绍了后端服务数据库和多线程并行处理优化,并示例了改造前后的伪代码逻辑。当然了,优化是无止境的,前人栽树后人乘凉。作为我们开发者来说,既然站在了巨...

    小柒2012
  • 聊聊Java进阶之并发基础技术—线程池剖析

    在JDK中,J.U.C并发包下的ThreadPoolExecutor核心类是一种基于Executor接口的线程池框架,将任务提交和任务执行解耦设计,其中Exec...

    用户2991389
  • 面试题-关于Java线程池一篇文章就够了

    线程池是一种多线程处理形式,处理过程中将任务提交到线程池,任务的执行交由线程池来管理。

    用户1161110
  • JAVA并发修炼手册 | 并发的概念

    它是互联网分布式系统架构设计中必须考虑的因素之一,通常是指,保证系统能够同时并行化处理海量请求

    battcn
  • 细说线程池---入门篇!!!

    什么是线程池? 在Java中,如果每个请求到达后就创建一个线程,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源...

    用户4143945

扫码关注云+社区

领取腾讯云代金券