一文理解并发三大特性:原子性,可见性,有序性

128 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情

在编写多线程代码遇见问题时,错误往往属于三种特性之一

原子性

即一个或一系列操作,要么全部发生,要么都不发生

比如经典的银行转账,从银行账户 A 到账户 B 的货币转账。它包括两个操作,从账户 A 取出钱并将其保存到账户 B,如果这两个操作中的任何一个失败,钱既不会丢失也不会产生

可见性

线程之间的同步离不开可见性,若要保证一个线程的的结果需要被另一个线程感知到,即需要保证可见性 看一段代码

  boolean flag = false; 

  //线程一执行
  void start() { 
    while (!falg) { 
    } 
  } 
 
  //线程二随线程一执行后执行
  void stop() { 
    flag = true; 
  } 

这段代码可能导致一个结果,当线程二更改flag之后,线程一仍然在死循环,这可能是因为当线程二更改了flag的值后,并没有将其刷新到主存当中,而是存储在高速缓存中,为了避免这种情况,可以将flag的赋值语句改为volatile boolean flag = false

对 volatile 的所有操作都以单一顺序发生,每次读取都会按该顺序看到最后一次写入。换句话说,编译器无法阻止对 volatile 的读取看到另一个线程执行的写入。

有序性

代码执行顺序的正确性,只有保证了正确的执行顺序才能在并发环境下获得正确的结果

int a = 1;
int b = 2;
int c = 3;

这段代码在执行的时候,并不会按照我们编写的顺序执行,可能是乱序倒序等等,只要最后的结果一致,编译器会在优化的角度来重新编排代码的执行顺序,在单线程的情况下没有影响,但在多线程并发下会影响到正确性

int a = 0;
int b = 0;
//线程一运行
int value(){
    int c = a;
    int d = b;
    return c+d;
}
//线程二运行
void change(){
    a = 1;
    b = 1;
    
}

value()的输出结果不一定是2,可能是1也可能是0,这一切都是因为没有保证有序性,可能在c读取a值的时候,因为重新编排的代码顺序,还没有执行a=1,可能先执行b=1,导致结果错误。

如何保证有序性呢?

  • 为变量加上volatile关键字
  • 为函数加锁