Java复习-并发编程中的三个问题:原子性、可见性和有序性

时间:2022-03-17 18:00:34

在并发编程中,我们通常会遇到以下三个问题:原子性问题,可见性问题,有序性问题。

1、原子性:

原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

2、可见性


可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

3、有序性

有序性:即程序执行的顺序按照代码的先后顺序执行。


当程序在运行过程中,会将运算需要的数据从主存中复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。这三个问题主要就是在这个从主存中读入数据到高速缓存及从高速缓存写出数据到主存的过程中,数据操作可能遇到的问题。

先列一份代码:

package BasicConcept;

import java.time.Year;

public class BasicConcurrent {

int x = 0;
int y = 0;

public static void main(String[] args) {
// TODO Auto-generated method stub
BasicConcurrent basicConcurrent = new BasicConcurrent();
Thread t1 = new Thread(basicConcurrent.new TrdOne());
Thread t2 = new Thread(basicConcurrent.new TrdTwo());
t1.start();
t2.start();
System.out.println("x + y = " + (basicConcurrent.x+basicConcurrent.y));
}

class TrdOne implements Runnable {

@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 20; i++) {
try {
x ++;
Thread.sleep(100);
y =x;
System.out.println("x = " + x + " " + "y = " + y + " " +Thread.currentThread().getName());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

}

class TrdTwo implements Runnable {

@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(20);
System.out.println("x = " + x + " "+ "y = " + y + " " +Thread.currentThread().getName());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

}


}


下面是上面的运行结果:

Java复习-并发编程中的三个问题:原子性、可见性和有序性Java复习-并发编程中的三个问题:原子性、可见性和有序性

我们分析一下,我们知道新的线程启动之后,在多核处理器上是各自运行的。上面的运行结果我们可以看到,主线程main中用来输出当前的x与y的值,而x与y的值是通过Thread0与Thread1去改变的。每次Thread0从主存中读取当前的x与y值,对x实现x++,之后输出x与y的值;而Thread1也是从主存中读取当前的x与y的值,之后输出x与y的值。

从结果中,我们可以看到在原子性方面如果想实现:一边x++后暂时之后再实现y=x,另一遍实现输出x与y,利用这两个线程可能出现x与y值并不等,每次极有可能出现x已经++了,但是此时挂起了Thread0,而Thread1并不能马上读出准确值,反而读的是之前主存里存放的x与y值,这个小情况就体现出原子性常见的问题。

而在可见性上,其实和之前的原子性有类似的原因,正是因为Thread0和Thread1的可能暂时挂起当前线程,极有可能产生Thread1每次读到的x与y值都是老值,都不是Thread0处理之后的值,这点可以视为其可见性方面的问题。

在有序性上,程序可能由于x和y没有数据依赖性,因此可能会被重排序。假如发生了重排序,在Thread0执行过程中先执行x++可能会暂时放弃执行y=x,而此时Thread1会执行输出,并不能保证到最后y与x一样大。



在此顺便提下volatile关键字,synchronized关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized,但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。通常来说,使用volatile必须具备以下2个条件:

1)对变量的写操作不依赖于当前值
2)该变量没有包含在具有其他变量的不变式中
实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。
就我的理解volatile关键字是保证了对象的可见性,但是无法保证其原子性,同时还能部分保证其有序性。