并发编程 — 1. 三大概念

原子性

一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
类似于数据库的事务的原子性,比如在银行转账时,两个账户进行读写操作,若不具有原子性则可能导致意想不到的结果。
再举个例子,一个简单的赋值语句 i=9,假若执行到这句话包括两个过程为低16为赋值和为高16为赋值。那么有可能发生一个线程修改完低16位,被中断。另外一个线程又去读取i的值,这时候就会读取到错误的数据。
 

可见性

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

//线程1执行的代码
int i = 0;
i = 10;

//线程2执行的代码
j = i;

假若线程1刚执行完i=10这句话还没来得及写回,线程2执行了 j=i,就会使得线程2拿到的 i  还是初始化的 0 而不是 10。

这就是可见性问题,线程1修改了 i 的值而线程2没有立即看到值的变化。 

 

有序性

程序执行的顺序按照代码的先后顺序执行
这个问题主要是因为 指令重排序,处理器为了提高程序的执行效率,可能会在不影响执行结果的前提下调整语句的执行顺序。举个例子

int a = 10; //语句1
int r = 2;	//语句2
a = a + 3;	//语句3
r = a * a;	//语句4

比如这段代码,处理器可能会进行指令重排序,让语句2在语句1前执行,但不会让语句4在语句3之前执行,因为那会影响执行结果。
但重排序只是保证了不影响单线程下执行结果,在多线程就有可能存在问题。假设一下情况:


//线程1:
context = loadContext();   //语句1
inited = true;             //语句2

//线程2:
while(!inited ){
  sleep()
}
doSomething(context);

在线程1中,语句1和语句2互相之间没有依赖,可能会被重排序,使得语句2先执行,这时候如果线程2去判断时会调出循环执行doSomething,而这时候context还未被初始化,会导致程序出错。

为了避免上述存在的不一致问题,JAVA虚拟机定义了一种java内存模型(Java Memory Model,JMM)来屏蔽各个硬件平台和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。

原子性

在java中,对基本数据类型的变量的读取和赋值(赋值 常量,而不包括赋值变量)都具有原子性的。

x = 10;        //语句1
y = x;         //语句2
x++;           //语句3
x = x + 1;     //语句4

上述语句中,只有语句1具有原子性。因为其他的语句都涉及2-3个步骤才能完成,比如语句2需要先读取x值再将值写入到y相应的内存中。语句3需要先读取x值,再加1,最后写入。要想实现更大范围操作的原子性,则需要通过synchronized或者lock来实现。

可见性

可见性,java通过volatile关键字来实现。
当一个共享变量被volatile修饰时,它会保证修改的值会被立即更新到内存,而读取时也会直接从内存读取新值。而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
另外,通过synchronized和Lock也能够保证可见性

有序性

在java中,可以通过volatile关键字来保证一定的“有序性”。另外可以通过synchronized和Lock来保证有序性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值