作者:JacobJenkov原文:http://tutorials.jenkov.com/j...翻译:潘神莲如果大家有更好的翻译版本,欢迎??提issue或投稿~更新时间:2022-02-24CAS(compareandswap)是一种用于并发算法设计的技术。基本上,CAS是将变量的值与预期值进行比较,如果值相等,则将变量的值交换为新值。CAS听起来可能有点复杂,但一旦你理解它实际上相当简单,所以让我进一步详细说明这个话题。顺便说一句,compareandswap有时是CAS的缩写,所以如果你看到一些关于并发的文章或视频提到CAS,那很可能指的是compareandswap(比较和交换)操作。CAS教程视频如果你喜欢视频,我有这个CAS视频教程版本:(绿色在线)))模型。checkthenact模式发生在代码首先检查变量的值然后对该值进行操作时。这是一个简单的例子:publicclassProblemmaticLock{privatevolatilebooleanlocked=false;publicvoidlock(){while(this.locked){//忙着等待-直到this.locked==false}this.locked=true;}publicvoidunlock(){this.locked=false;}}此代码不是100%正确的多线程锁实现。这就是我将其命名为ProblemmaticLock的原因。但是,我创建了这个错误的实现来说明如何使用CAS功能绕过它。lock()方法首先检查成员变量locked是否等于false。这是在while循环内完成的。如果locked变量为false,lock()方法将离开while循环并将locked设置为true。换句话说,lock()方法首先检查锁定变量的值,然后根据该检查采取行动。先检查,后执行。如果多个线程几乎同时访问同一个ProblemmaticLock实例,那么上面的lock()方法就会出现一些问题,例如:如果线程A检查锁定的值为false(预期值),它会退出while-loop循环执行后续逻辑。如果此时有线程B在线程A设置locked值为true之前检查locked的值,那么线程B也会退出while循环执行后续逻辑。这是一个典型的资源竞争问题。CheckThenAct必须是原子的为了在多线程应用程序中正常工作(避免资源竞争),CheckThenAct必须是原子的。原子性意味着检查和执行操作作为原子(不可分割的)代码块执行。任何开始执行该块的线程都将完成该块的执行,而不会受到其他线程的干扰。不允许其他线程同时执行同一个原子块。使Java代码块成为原子的一种简单方法是使用Java的synchronized关键字对其进行标记。请参阅同步。这就是ProblemmaticLock之前使用synchronized关键字将lock()方法转换为原子代码块的方式:publicclassMyLock{privatevolatilebooleanlocked=false;publicsynchronizedvoidlock(){while(this.locked){//忙着等待-直到this.locked==false}this.locked=true;}publicvoidunlock(){this.locked=false;}}现在方法lock()已经声明了同步,所以同一个实例的lock()方法在同一时间执行,只允许一个线程访问。相当于lock()方法是原子的。阻塞线程代价高昂当两个线程试图同时进入Java中的同步块时,其中一个线程将被阻塞,而另一个线程将被允许进入同步块。当进入同步块的线程再次退出该块时,等待的线程将被允许进入该块。如果允许线程访问执行,则输入同步代码块的开销不是很大。但是如果另一个线程因为另一个线程已经在synchronized块中执行而被迫等待阻塞,那么这个阻塞线程的成本是非常高的。此外,当同步块再次空闲时,您无法准确确定何时可以解除阻塞的线程。通常由操作系统或执行平台来协调阻塞线程的解除阻塞。当然,被阻塞的线程被解除阻塞并被允许进入之前不会花几秒或几分钟,但是一些时间可能会浪费在被阻塞的线程上,因为它可以访问共享数据结构。此处对此进行了解释:硬件提供的原子CAS操作现代CPU内置了对原子CAS操作的支持。在某些情况下,可以使用CAS操作来代替同步块或其他阻塞数据结构。CPU保证一次只有一个线程可以执行CAS操作,即使跨CPU内核也是如此。代码后面有示例。当使用硬件或CPU提供的CAS函数代替操作系统或执行平台提供的synchronized、lock、mutex(互斥锁)时,操作系统或执行平台不需要处理阻塞和解阻塞线程。这允许使用CAS的线程等待更少的时间来执行操作,从而减少拥塞并提高吞吐量。下图说明了这一点:如您所见,试图进入共享数据结构的线程永远不会被完全阻塞。它不断尝试执行CAS操作,直到成功并被允许访问共享数据结构。这样,线程可以访问共享数据结构之前的延迟就会最小化。当然,如果一个线程在重复的CAS期间等待很长时间,它会浪费大量本可以用于其他任务(其他线程)的CPU周期。但在很多情况下,情况并非如此。这取决于另一个线程使用共享数据结构的时间。实际上,共享数据结构的使用时间不会很长,因此上述情况应该不会经常发生。但这又取决于情况、代码、数据结构、尝试访问数据结构的线程数、系统负载等。相比之下,阻塞线程根本不使用CPU。Java中的CAS从Java5开始,您可以通过java.util.concurrent.atomic包中的一些新原子类来访问CPU级别的CAS方法。这些类是:AtomicBooleanAtomicIntegerAtomicLongAtomicReferenceAtomicStampedReferenceAtomicIntegerArrayAtomicLongArrayAtomicReferenceArray使用Java5+中包含的CAS功能而不是自己实现它的优点是Java5+中的内置CAS功能允许您的应用程序利用CPU的底层功能来执行CAS操作。这使您的CAS实现代码更快。CAS的保证CAS功能可以用来保护临界区(CriticalSection),从而防止多个线程同时执行临界区。?>临界区是每个线程中访问临界资源的代码。无论是硬件关键资源还是软件关键资源,多个线程都必须互斥地访问它。每个线程中访问临界资源的代码段称为临界区(CriticalSection)。程序中每个线程中访问临界资源的部分称为临界区(CriticalSection)(临界资源是一种共享资源,一次只允许一个线程使用)。临界区一次只允许一个线程进入,其他线程进入后不允许再进入。下面的示例演示如何使用AtomicBoolean类的CAS功能来实现前面显示的lock()方法,从而保证一次只有一个线程可以退出lock()方法。公共类CompareAndSwapLock{privateAtomicBooleanlocked=newAtomicBoolean(false);publicvoidunlock(){这个。锁定。设置(假);}publicvoidlock(){while(!this.locked.compareAndSet(false,true)){//忙等待-直到compareAndSet()成功}}}注意,锁定的变量不再是Boolean类型,而是AtomicBoolean类型.该类有一个compareAndSet()方法,将实例(变量locked)的值与第一个参数(false)进行比较,如果比较结果相同(即locked的值等于第一个参数false),则locked实例的值将与期望值true交换(即locked变量设置为true,表示被锁定)。如果交换成功,compareAndSet()方法返回true,如果交换不成功,则返回false。在上面的示例中,compareAndSet()方法调用将锁定变量的值与false的值进行比较,如果锁定变量的结果值为false,则将锁定值设置为true。由于一次只允许一个线程执行compareAndSet()方法,因此只有一个线程能够将AtomicBoolean实例值视为false,从而将其交换为true。因此,一次只能有一个线程退出while-loop(while循环),通过调用unlock()方法将locked设置为false,这样一次只有一个线程的CompareAndSwapLock被解锁。CAS实现乐观锁也可以使用CAS函数作为乐观锁机制。乐观锁机制允许多个线程同时进入临界区,但只允许其中一个线程在临界区结束时提交其工作。以下是使用乐观锁定策略的并发计数器类的示例:publicclassOptimisticLockCounter{privateAtomicLongcount=newAtomicLong();publicvoidinc(){booleanincSuccessful=false;while(!incSuccessful){longvalue=this.count.得到();长新值=值+1;incSuccessful=this.count.compareAndSet(value,newValue);}}publiclonggetCount(){returnthis.count.get();}}注意inc()方法是HowtogettheexistingcountvaluefromAtomicLonginstancevariablecount。然后根据旧值计算新值。最后,inc()方法尝试通过调用AtomicLong实例的compareAndSet()方法来设置新值。如果AtomicLong实例值count仍然和上次获取的值一样(longvalue=this.count.get()),那么compareAndSet()就会执行成功。但是,如果同时有另一个线程调用增加AtomicLong实例的值(指之前成功调用compareAndSet()方法的线程,一般认为是资源竞争),则compareAndSet()调用将失败,因为预期值value不再等于AtomicLong中存储的值(原始值已被前一个线程更改)。在这种情况下,inc()方法将在while循环(while循环)中进行另一次迭代,并尝试再次递增AtomicLong值。(本文完结)原文:http://tutorials.jenkov.com/j...译文:潘神莲如果大家有更好的译本,欢迎??提issue或投稿~
