首页 > 编程知识 正文

java语言程序设计基础篇答案,java编程入门基础

时间:2023-05-03 09:21:22 阅读:172237 作者:221

原子(atomic )表示“不可再分割的最小粒子”,而原子操作(atomic operation )表示“一个或多个不可中断的操作”。 在多处理器上实现原子操作变得有点复杂。 我们来谈谈英特尔处理器和Java是如何实现原子操作的。

1 .术语定义

在理解原子操作实现原理之前,必须理解相关术语,如表2-7所示。

2 .处理器实现原子操作的方法

32位IA-32处理器使用基于高速缓存锁或总线锁的方法来提供多处理器之间的原子操作。 首先,处理器自动保证基本的存储器操作的原子性。 确保处理器从系统内存中读取或写入一个字节意味着,当一个处理器读取一个字节时,其他处理器无法访问该字节的存储器地址。 奔腾6和最新的处理器可自动确保单个处理器在同一个高速缓存线上执行16/32/64位操作,但复杂的内存操作过程,包括总线宽度、多条高速缓存线和电子表格表访问但是,处理器提供了总线锁和高速缓存锁两种机制,保证了复杂内存操作的原子性。

)1)通过总线锁定保证原子性

第一种机制是用总线锁保证原子性。 当多个处理器同时对共享变量进行读写操作时,I是经典的读写操作。 由于共享变量由多个处理器同时操作,所以读改写操作不是原子的,操作完成后共享变量的值与期望不一致。 例如,如果i=1,执行两次I操作,预期结果为3,但结果可能为2,如图2-3所示。

这可能是因为多个处理器同时从各自的高速缓存读取变量I,将每个变量加1,然后将每个变量写入系统内存。 那么,为了保证读写共享变量的操作是原子的,当CPU1读写共享变量时,必须保证CPU2不能操作缓存了该共享变量的存储器地址的缓存。

处理器使用总线锁解决这个问题。 总线锁定是指通过使用处理器提供LOCK#信号,在某个处理器向总线输出该信号时,其他处理器的请求被阻塞的情况下

处理器可以独占共享存储器。

)2)通过缓存锁定保证原子性

第二种机制是通过缓存锁保证原子性。 同时,只要保证对某个存储器地址操作是原子的即可,但是总线锁锁定CPU和存储器间的通信,在锁定期间,由于其他处理器不能操作其他存储器地址的数据,所以总线锁的开销很大,目前

由于经常使用的内存被缓存在处理器的L1、L2、L3高速缓存中,所以原子操作可以直接在处理器内部高速缓存中进行,不需要声明总线锁定,奔腾6和现在的处理器都使用“高速缓存锁定”方式“高速缓存锁定”是指如果内存空间被高速缓存在处理器的高速缓存行中,并且在锁定操作期间被锁定,则在执行锁定操作并写回内存时,处理器将在总线上修改内部存储器地址,而不发出LOCK#信号因为高速缓存一致性程序阻止两个或多个处理器同时修改高速缓存内存空间中的数据。 如果其他处理器写回锁定的高速缓存线中的数据,高速缓存线将变为无效。 在图2-3所示的示例中,如果CPU1在更改高速缓存线I时使用高速缓存锁,则CPU2将无法同时高速缓存I的高速缓存线。

但是,如果CPU不使用高速缓存锁,则有两种情况。

第一种情况是,如果处理器无法在内部缓存所操作的数据,或者所操作的数据跨越多条高速缓存线,则处理器将调用总线锁。

第二种情况是某些处理器不支持高速缓存锁定。 对于英特尔486和奔腾处理器,即使锁定的内存空间位于处理器缓存线上,也会调用总线锁。

这两种机制都是通过英特尔处理器提供许多锁定前缀的指令实现的。 例如BTS、BTR、BTC; 交换命令XADD、CMPXCHG和其他操作数和逻辑命令(如ADD和OR )会锁定这些命令操作的内存区域,从而导致其他处理器无法同时访问。

3.Java实现原子操作的方法

在Java中,通过锁定CAS并使其循环可以实现原子操作。

(1)基于循环CAS的原子操作

利用处理器提供的CMPXCHG指令实现JVM中的CAS操作。 安装自旋CAS的基本思路是循环直到CAS操作成功。 以下代码安装了基于CAS线程安全的计数器方法safeCount和非线程安全的计数器count。

/**AliPay.comInc.*copyright(c ) 2004-2015 all rights reserved.*/package chapter 02; import java.util.ArrayList; import java.util.List; import Java.util.concurrent.atomic.atomic integer; /** *计数器* * @ author tengfei.fangtf * @ version $ id : snippet.Java,v 0.1 2015-7-31下午1336032336042 tengfei.fan

vate int i = 0; public static void main(String[] args) { final Counter cas = new Counter(); List<Thread> ts = new ArrayList<Thread>(600); long start = System.currentTimeMillis(); for (int j = 0; j < 100; j++) { Thread t = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10000; i++) { cas.count(); cas.safeCount(); } } }); ts.add(t); } for (Thread t : ts) { t.start(); } // 等待所有线程执行完成 for (Thread t : ts) { try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(cas.i); System.out.println(cas.atomicI.get()); System.out.println(System.currentTimeMillis() - start); } /** * 使用CAS实现线程安全计数器 */ private void safeCount() { for (;;) { int i = atomicI.get(); boolean suc = atomicI.compareAndSet(i, ++i); if (suc) { break; } } } /** * 非线程安全计数器 */ private void count() { i++; }}

从Java 1.5开始,JDK的并发包里提供了一些类来支持原子操作,如AtomicBoolean(用原子方式更新的boolean值)、AtomicInteger(用原子方式更新的int值)和AtomicLong(用原子方式更新的long值)。这些原子包装类还提供了有用的工具方法,比如以原子的方式将当前值自增1和自减1。
(2)CAS实现原子操作的三大问题
在Java并发包中有一些并发框架也使用了自旋CAS的方式来实现原子操作,比如LinkedTransferQueue类的Xfer方法。CAS虽然很高效地解决了原子操作,但是CAS仍然存在三
大问题。ABA问题,循环时间长开销大,以及只能保证一个共享变量的原子操作。

 

1)ABA问题。因为CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加1,那么A→B→A就会变成1A→2B→3A。从Java 1.5开始,JDK的Atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

public boolean compareAndSet(
V expectedReference, // 预期引用
V newReference, // 更新后的引用
int expectedStamp, // 预期标志
int newSt

)

2)循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如
果JVM能支持处理器提供的pause指令,那么效率会有一定的提升。pause指令有两个作用:第
一,它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间
取决于具体实现的版本,在一些处理器上延迟时间是零;第二,它可以避免在退出循环的时候
因内存顺序冲突(Memory Order Violation)而引起CPU流水线被清空(CPU Pipeline Flush),从而
提高CPU的执行效率。
3)只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循
环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子
性,这个时候就可以用锁。还有一个取巧的办法,就是把多个共享变量合并成一个共享变量来
操作。比如,有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java 1.5开始,
JDK提供了AtomicReference类来保证引用对象之间的原子性,就可以把多个变量放在一个对
象里来进行CAS操作。
(3)使用锁机制实现原子操作
锁机制保证了只有获得锁的线程才能够操作锁定的内存区域。JVM内部实现了很多种锁
机制,有偏向锁、轻量级锁和互斥锁。有意思的是除了偏向锁,JVM实现锁的方式都用了循环
CAS,即当一个线程想进入同步块的时候使用循环CAS的方式来获取锁,当它退出同步块的时
候使用循环CAS释放锁。amp // 更新后的标志
)
注:本文源自《Java并发编程的艺术》一文。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。