Java并发之Java内存模型以及线程三个性质

Java内存模型

Java内存模型定义了线程和主存之间的抽象关系:线程之间的共享变量存储在主存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程共享变量的副本。

Java内存模型控制线程之间的通信,它决定一个线程对主存共享变量的写入何时对另外一个线程可见。

Java中的堆内存用来存储对象实例,堆内存是被所有线程共享的运行时内存区域,因此它存在内存可见性的问题。而局部变量、方法定义的参数则不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型影响。

Java内存模型的抽象示意图:

Java并发之Java内存模型以及线程三个性质_第1张图片
Java内存模型的抽象示意图,JMM=Java Memory Model

两线程通信,必须要经历下面两个步骤:

  1. 线程A把线程A本地内存中更新过的共享变量刷新到主内存中。
  2. 线程B到主存中去读取线程A之前已经更新过的共享变量。由此可见,如果我们执行下面的语句:
    int i3;
    执行线程必须先在自己的工作线程中对变量所在的缓存进行赋值操作,然后再写入主存当中,而不是直接将数值3写入主存当中。

线程安全的三个性质

原子性

对基本数据类型的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行完毕,要么不执行。
注意补充:赋值操作必须是将基本数据类型赋值给某个变量,变量之间的相互赋值不是原子操作)

例子:

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

只有语句1是原子性操作,其他三个语句都不是原子性操作。
语句1是直接将数值10赋值给x,也就是说线程执行这个语句的会直接将数值10写入到工作内存中。
语句2实际上包含2个操作,它先要去读取x的值,再将x的值写入工作内存,虽然读取x的值以及 将x的值写入工作内存 这2个操作都是原子性操作,但是合起来就不是原子性操作了。
同样的,x++和 x = x+1包括3个操作:读取x的值,进行加1操作,写入新的值。

可以有以下结论:
1.并不是一个语句表示原子性操作,需要看这个语句中有多少个操作。
java.util.concurrent.atomic包中提供很多类使用了很高效的机器级指令(而不是使用锁)来保证其他操作的原子性。例如:AtomicInteger类提供了方法incrementAndGet和decrementAndGet,它们分别以原子方式将一个整数自增和自减。可以安全地使用AtomicInteger类作为共享计数器而无需同步。另外这个包还有AtomicBoolean、AtomicLong和AtomicReference这些原子类。

可见性

可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果,另一个线程马上就能看到。

对于可见性,Java提供了volatile关键字来保证可见性。
当一个共享变量被volatile修饰时,它会保证修改的值立即被更新到主存,所以对其它线程是可见的。当其他线程需要读取该值时,其它线程会去主存中读取新值。而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,并不会立即写入主存,何时被写入主存也是不确定的。当其他线程去读取该值时,此时主存中可能还是原来的旧值,这样就无法保证可见性。

有序性

Java内存模型中允许编译器和处理器对指令进行重排序,虽然重排序过程不会影响到单线程执行的正确性,但是会影响到多线程并发执行的正确性。这是可以通过volatile来保证有序性。

除了volatile,也可以通过synchronized和Lock来保证有序性。它们两保证每个时刻只有一个线程执行同步代码,这相当于是让线程顺序执行同步代码,从而保证了有序性。

你可能感兴趣的:(Java并发之Java内存模型以及线程三个性质)