synchronized 加锁原理,AtomicInteger如何保证原子性的?

240 阅读2分钟

先看个代码

image.png

image.png 如上图,声明一个对象V ,内含increase方法对value++,getValue 获取Value的值,代码中创建一个线程1 ,在内部循环1000W次,调用increase,主线程也循环1000W次调用increase,最后打印结果, 按道理,value初始值为0,最后的结果应该是 2000W ,但我们输出的结果显然不是,证明我们的代码是有问题的,看下increase 方法,如下图,在并发的时候会产生线程安全问题的,怎麽解决这个问题呢?有人很快想到了对increase方法加锁,一定是可行的,我们先看下

image.png

加锁后的执行结果是没问题的,这里画张图说下synchronized 加锁的原理 image.png

如下图,JDK1.6之前synchronized 是互斥锁,加锁实际上是对 this 对象的monitor进行加锁,多线程竞争时,抢不到锁的线程2,3 会进入队列等待线程1释放锁; JDK1.6 对synchronized 进行的优化归根结底就是减少了锁竞争,通过偏向锁,自旋等方式避免用户态和内核态的频繁切换. image.png

那么,除了加锁还有哪些方法?第二种方法就是原子操作,就是大家都熟悉的AtomicInteger,我们改造一下代码运行一下,看下结果:

image.png

image.png

聊到这里就顺便说一下AtomicInteger 保证原子操作的原理,无论是JDK1.7还是1.8 底层都是通过自旋+CAS的方式实现的, 1.7源码

public final int incrementAndGet() {
    //自旋锁
    for (;;) {
        //获取volatitle修饰的变量,最新的主存值
        int current = get();
        //理论上自增值
        int next = current + 1;        
        if (compareAndSet(current, next))
            return next;
    }
}
 
 
public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

1.8源码

/**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
    public final int getAndAddInt(Object obj, long valueOffset, int delta) {
        int expect;
        //自旋
        do {
            //获取主存的值
            expect = this.getIntVolatile(obj, valueOffset);
        //CAS操作
        } while(!this.compareAndSwapInt(obj, valueOffset, expect, expect + delta));
 
        //返回旧值
        return expect;
    }