同步包括两方面的含义: 独占性和可见性。
很多人仅仅理解了独占性,而忽略了可见性。
根据Java Language Specification中的说明, jvm系统中存在一个主内存(Main Memory或Java Heap Memory),Java中所有变量都储存在主存中,对于所有线程都是共享的。
每条线程都有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。
一种常见的错误是,只有在写入共享变量时才需要同步,而读取的时候并不需要同步。
publicclass GetAndSet {
privateint i;
publicint getI() {
returni;
}
publicsynchronized void setI(int i) {
this.i = i;
}
}
在多线程环境中,仅从独占性上分析,上述程序根本不存在任何问题。
问题出在了可见性上,
即,如果调用
在线程A中:setI(10);
然后在线程B中:getI();
得到的很可能并非10,这个我们想要的答案。
这是因为并没有什么保证,在线程A设置的I,在线程B中可以立马看到当前值。
Java内存模型就是为了解决可见性的问题。
JMM规定了JVM必须遵循的一组最小保证,这组保证规定了对变量的写入操作在何时将对于其他线程是可见的。
这组保证就是Happen-Before 规则。
happen before规则:
Each action in a thread happens before everyaction in that thread that comes later in the program's order.
An unlock on a monitor happens before everysubsequent lock on that same monitor.
A write to a volatile field happens before everysubsequent read of that same volatile.
A call to start() on a thread happens before anyactions in the started thread.
All actions in a thread happen before any otherthread successfully returns from a join() on that thread.
happen before翻译过来就是:
程序顺序规则:一个线程中的每个操作,happens- before 于该线程中的任意后续操作。
监视器锁规则:对一个监视器锁的解锁,happens- before 于随后对这个监视器锁的加锁。
volatile变量规则:对一个volatile域的写,happens- before 于任意后续对这个volatile域的读。
传递性:如果Ahappens- before B,且B happens- before C,那么A happens- before C。
Thread.start()的调用会happens-before于启动线程里面的动作。
Thread中的所有动作都happens-before于其他线程从Thread.join中成功返回。
Happen-Before 规则是由JVM提供的,因此无论有没有“同步协助(synchronized,lock ,volatile,final)”,程序执行时都必须遵守这些规则, 只不过这些规则中的大部分和同步协助有关系。
注意:第一条程序顺序规则,按字面意思的话,在同一个线程中所有语句的执行过程,必须按照程序流的先后去执行。不可能有任何重排序的可能。
实际情况并非如此,在同一个线程中,只有有数据相关的操作才不会被重排序,而没有任何关系的操作是可以被编译器发现,并进行适当重排的。
如:inti =10; --------------a
intj =1;----------------------b
i =i+1;----------------------c
虽然 a hanppen before b, 可在真实执行的时候,b有可能会在a前执行。因为该线程根本无法感知这种顺序的变化。
可是,a 不可能在c后面执行,因为这个线程本身可以感知这个顺序的变化。