吃透Java内存模型 – 多线程同步

上节说到JMM不能保证线程间的可⻅性。而程序员可以通过同步编程来保证多线程运行的结果做到顺序一致性。那么Java程序中有哪些同步线程的方式呢。

同步锁

根据happens before监视器锁的原则。可以保证释放监视器锁的操作要发生在获取监视器锁的操作之前。那么多线程可以通过共享一个监视器锁来控制他们之间的执行顺序。一个线程获取监视器锁之后在其释放监视器锁之前。他将拥有在内存中执行的权利。处理器会认为内存要在这个线程释放监视器锁之后才能分配给其他其他线程

还是以上节的程序为例

public class ExecuteSequenceExample {

String name = "frank";

Boolean printFlag = false;

public synchronized void update() {

//这两个操作在监视器锁的临界区内可以重排序

printFlag = true; //1

name = "laona"; //2

}

public synchronized void printName() {

//这两个操作在监视器锁的临界区内可以重排序

if(printFlag) { //3

System.out.print("may name is:" + name);//4

}

}

}

假如我们对update方法和printName方法都加了监视器锁,在使用了监视器锁的情况下程序在JMM内存中的执行过程中1和2 在监视器锁的临界区内可以重排序,3.和4在临界区内也可以重排序。但是在临界区内的重排序不会影响程序的最终执行结果。因为一个线程获得了监视器锁在运行完update方法临界区内代码并释放监视器锁之前,其他线程是不能够读取执行printName方法的。这样程序在多线程场景中运行的最终结果和顺序一致性模型就一致了。

volatile关键字

根据happens-before原则,对于volatile修饰的域的写操作要发生在读操作之前。这样对一个volatile变量的读,总能看到任意线程对这个volatile变量的写入。从而保证了变量的内存可见性。因为当一个一个线程对volatile变量写时,JMM会把该线程对应的本地内存中的共享变量刷新到主存中。而当一个线程读一个volatile变量时,JMM 会把该线程本地内存值为无效,接着从主存中读取共享变量的值。相对于锁能保证执行具有原子性,volatile只能在可见性上得到保证,而对于复合操作例如 i++这种操作不具有原子性。

final关键字

final关键字修饰的域的读和写相当于常量的访问。对于final域,编译器和处理器遵守两个重排序规则

在构造函数内对一个final域的写入,与随后把这个被构造函数对象的引用赋给一个引用变量,这两个操作之间不能重排序初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序

(0)
小多多的头像小多多创始人

相关推荐

发表回复

登录后才能评论