生活
生活就是生下来,活下去。
————
在JAVA并发编程,如果要保证程序的线程安全,就要保证代码的原子性、可见性、有序性。
今天这一篇先来聊聊原子性。
什么是原子性?
原子性,即一个操作或多个操作,要么全部执行并且在执行的过程中不被打断,要么全部不执行。(提供了互斥访问,在同一时刻只有一个线程进行访问)
JAVA本身的原子性
由JAVA内存模型来直接保证具有原子性变量操作 的有read/load/use/assign/store/write,也就是说对于基本数据类型的赋值,读取操作时具有原子性的。(在32位系统下对于long类型的赋值读取并不是原子性的)。
下面具有原子性的操作有?
x=1;
y=x;
x++;
x=x+1;
第一条 x =1,是一个单纯的赋值操作,满足原子性。
第二条 y=x ,实际是两个操作,分别是 读取x变量 ,将x赋值给y,这两个操作分别来看都是原子性的,但是合起来就不是了。
第三条 x++,实际是三个操作 ,先读取变量 ,在进行+1操作 ,再赋值给x,不满足原子性
第四条 x=x+1 同上,不满足原子性
看个不满足原子性的案例
public class YZXTest {
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(1000);
for(int i=0;i<1000;i++) {
new Thread(new Inc(latch)).start();
}
try {
latch.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Inc.num);
}
static class Inc implements Runnable{
private CountDownLatch latch;
public static int num;
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
num++;
latch.countDown();
}
public Inc(CountDownLatch latch) {
super();
this.latch = latch;
}
}
}
期望值1000,实际上跑了几次都在1000以下,说明num++这个操作不满足原子性。
JAVA提供了原子性的技术保障有如下:
1、synchronized (互斥锁)
2、Lock(互斥锁)
3、原子类(CAS 乐观锁)
上面两个都是通过互斥锁实现,即同一时刻只允许一个线程操作该变量,保障了原子性,没啥好说的。
原子类的实现可以看下。
int变量对应的原子类 AtomicInteger
所以如下的代码,通过原子类来实现原子性,具体代码修改如下:
public static int num public static AtomicInteger num = new AtomicInteger(0);
num++ num.incrementAndGet();
System.out.println(Inc.num); System.out.println(Inc.num.get());
结果跑了几次都是1000,说明确实实现了原子性。
原子类进行++的操作细节都在 incrementAndGet里,简单的看下源码:
调用到unsafe里的方法
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失败的概率很高,长时间自旋导致CPU消耗较大
后记
埋两个坑
关于乐观锁、悲观锁。
关于CAS的ABA问题 后续再聊。