数据竞争和先行原则
先行原则规定了先行的操作对后续的操作可见,有效解决了数据竞争问题。数据竞争问题发生在当变量被多个线程读时,同时至少有一个线程在对这个变量进行写,那么写入的变量不一定能够及时被其他线程看见,甚至永远也不能被其他线程看见。
从这里就可以看出,“先行”其实规定的是“可见性”问题,它和操作(读、写、方法调用等)发生的时间先后是两个概念,时间上先发生的,对后发生的不一定可见(当然,没发生的事情,必然不可见)。再次强调,“先行”强调的是“可见性”。
java虚拟机规范有这样的说明:
Two actions can be ordered by a happens-before relationship. If one action happens-before another, then the first is visible to and ordered before the second.
If we have two actions x and y, we write hb(x, y) to indicate that x happens-before y.
-
If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).
-
There is a happens-before edge from the end of a constructor of an object to the start of a finalizer (§12.6) for that object.
-
If an action x synchronizes-with a following action y, then we also have hb(x, y).
The wait
methods of class Object
(§17.2.1) have lock and unlock actions associated with them; their happens-before relationships are defined by these associated actions.
It should be noted that the presence of a happens-before relationship between two actions does not necessarily imply that they have to take place in that order in an implementation. If the reordering produces results consistent with a legal execution, it is not illegal.
For example, the write of a default value to every field of an object constructed by a thread need not happen before the beginning of that thread, as long as no read ever observes that fact.
More specifically, if two actions share a happens-before relationship, they do not necessarily have to appear to have happened in that order to any code with which they do not share a happens-before relationship. Writes in one thread that are in a data race with reads in another thread may, for example, appear to occur out of order to those reads.
The happens-before relation defines when data races take place.
A set of synchronization edges, S, is sufficient if it is the minimal set such that the transitive closure of S with the program order determines all of the happens-before edges in the execution. This set is unique.
It follows from the above definitions that:
-
An unlock on a monitor happens-before every subsequent lock on that monitor.
-
A write to a
volatile
field (§8.3.1.4) happens-before every subsequent read of that field. -
A call to
start()
on a thread happens-before any actions in the started thread. -
All actions in a thread happen-before any other thread successfully returns from a
join()
on that thread. -
The default initialization of any object happens-before any other actions (other than default-writes) of a program.
When a program contains two conflicting accesses (§17.4.1) that are not ordered by a happens-before relationship, it is said to contain a data race.
The semantics of operations other than inter-thread actions, such as reads of array lengths (§10.7), executions of checked casts (§5.5, §15.16), and invocations of virtual methods (§15.12), are not directly affected by data races.
Therefore, a data race cannot cause incorrect behavior such as returning the wrong length for an array.
A program is correctly synchronized if and only if all sequentially consistent executions are free of data races.