AtomicInteger
public static void main(String[] args) { AtomicInteger ai = new AtomicInteger(20); System.out.println(ai.incrementAndGet()); }
在学习AtominInteger时,第一次接触了CAS,也就是在自增的时候使用CAS自旋锁解决并发问题。调用的方法时incrementAndGet方法。而这个自增的操作本质上不是一个原子性操作,但是改方法在没有加synchronized的情况下实现了线程安全。下面深入学习CAS:
/** * Atomically increments by one the current value. * 以原子性的方法,将当前值+1 * @return the updated value */ public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; }
这里用到了unsafe变量,该变量比较重要,以后解答,先看getAndAddInt方法
//操作源码,传入三个值,V,A,B,第一个V是AtomicInteger维护的变量value,表示当前数的实际值,他被volatile修饰来保证其可见性。第二个值A为当前希望操作的值,B为当前希望修改后的值。 public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }
整个CAS贯穿三个值,V,A,B。首先CAS,compareAndSet方法将A与实际值V进行比对,如果相等,那么就可以操作修改为B,如果不等那就放弃操作。
这样保证了修改的一致性,但是不能保证修改的原子性,因为他有两个操作,一个是比对,第二个是修改。假设现在线程1需要将value = 1修改为2,那么比对A=1和V=1,发现相同那么修改。此时线程2比对A=1和V=1,发现也相同,把V修改为3。线程2执行完毕,而在线程1中,此时的V已经是3了,但是线程1已经经历过了比对操作,将3又修改为了2,这样就造成了严重的原子性问题。
这里是基于HotSpot底层调用操作系统来完成原子性操作的,在操作系统执行比对并修改的CAS操作时,加上了底层锁,保证了操作原子性,而我们使用Java的时候全然不知。这是操作系统内存的缓存行锁,也有可能是总线锁(这是属于硬件级别的锁,但是在我们的代码中看是没有锁的)。
最终实现(CPU汇编指令):
在最底层采用了lock cmpxchg 指令实现了cas(cmpxchg不是原子性的,所以加入lock指令)
硬件:
lock指令在执行后面指令的时候锁定一个北桥信号(不采用锁总线的方式)
用户态和内核态:
早期一些设备与计算机连接就可以直接使用底层方法操作硬件,这就意味着如果操作不当可能会让硬件死机等一些操作。为此有了用户态和内核态,将计算机最底层操作硬件的方法都集成到内核态进行统一处理,内核态一般都是操作系统所在的范围。而我们使用的jvm对于操作系统来说只是一个小程序,jvm属于用户态。当我们需要加锁时,如果一上来就给内核加锁来回切换用户态内核态,那就会造成极大的内存消耗,而cas操作就没有。