With the help of synchronization according to the happens-before law
At the beginning of the article, let's look at a piece of code and its implementation:
public class PossibleRecording{
static int x=0, y=0;
static int a=0, b=0;
public static void main(String[] args) throws InterruptedException {
Thread threadOne=new Thread(new Runnable() {
@Override
public void run() {
a=1;
y=b;
}
});
Thread threadTwo=new Thread(new Runnable() {
@Override
public void run() {
b=1;
x=a;
}
});
threadOne.start();
threadTwo.start();
threadOne.join();
threadTwo.join();
System.out.println("(" + x + " , " + y + ")");
}
}
Results of the:
(0 , 1)
(1 , 0)
(1 , 1)
(0 , 0)
For the above paragraph and its simple code, it is easy to imagine how the program prints (0 , 1) or (1 , 0) or (1 , 1), the thread One can be in the thread Two Finish before starting, Thread Two can also finish before Thread One starts, or they can finish alternately, but the strange thing is that the program can print (0 , 0), the figure below shows a print (0, 0) 0) (because the actions in each thread do not depend on the data flow of other threads, these actions can be executed out of order):
In order to improve performance, compilers and processors will reorder instructions during program execution.Memory-level reordering can make programs behave unpredictably, and synchronization Suppresses the compiler, runtime, and hardware reordering of various ways of saving to disk that would otherwise break the visibility guarantees provided by JMM, which ensures that on different compilers and different processor platforms, through Insert a specific type of Memory Barrier to prohibit specific types of compiler reordering and processor reordering, providing consistent visibility guarantees for upper layers,
In the case of correct use of synchronization and locks, when will thread One modify the value of variable a to be visible to thread Two? We can't specify for all scenarios when variables modified by one thread are visible to other threads, but we can specify certain rules.This rule is happens-before.Since JDK 5, JMM is Use the concept of happens-before to illustrate memory visibility between multiple threads,
The happens-before principle is defined as follows:
- If an operation happens-before another operation, the result of the first operation will be visible to the second operation, and the first operation will be executed in the order before the second operation,
- The existence of a happens-before relationship between the two operations does not mean that they must be executed in the order specified by the happens-before principle.If the execution result after reordering is the same as the execution result according to the happens-before relationship , then this reordering is not illegal,
happens-before rules include:
- Program Order Law: Every action A in a thread happens-before every action B in that thread, where in the program, all actions B appear in actions After A
- Monitor Lock Law:The unlocking of a monitor lock happens-before each subsequent lock on the same monitor lock
- The Law of Volatile Variables: A write to a volatile field happens-before every subsequent read of the same field
- The Law of Thread Start: In a thread, a call to the Thread.start() method happens-before every action in the starting thread
- Thread termination rule: Any action in the county happens-before other threads detect that this thread has terminated, or the call to Thread.join() is successfully returned.Or Thread.isAlive() returns false
- Interruption Law:A thread calling another thread's interrupt happens-before the interrupted thread finds the interruption (either by throwing an InterruptedException, or by calling isInterrupted and interrupted) >
- Law of Finalization: The end of an object's construct happens-before the beginning of the object's finalizer
- Transitivity: If A happens-before B and B happens-before C, then A happens-before C
When a variable is read by multiple threads and written by at least one thread, if the read and write operations are not in order, there will be a data race.A correctly synchronized thread has no data race.Programs, locking
, unlocking
, reading and writing volatile variables
, starting a thread and checking if the thread ends
code>Such operations are synchronous actions,
FutureTask source code interpretation
Let's take a look at how the happens-before rule is cleverly used in FutureTask,
The most important variables in FutureTask are the two marked in the figure above,
- state: is a volatile modified variable used to represent the state of the current task
- outcome: used for normal results returned by get(), may also be exceptions
Pay attention to the comments after the outcome.There are few such comments in the jdk source code.Once there are such comments, it must be very important.
In theory, the outcome will be accessed by multiple threads.One thread should be able to read and write, and the other threads can only be read.In this case, why not add volatile? The advantage of adding volatile is that after the outcome and state variables are modified, other threads can immediately perceive it, but why didn't the author add volatile?
In the entire class, there are only two places for the write operation with the outcome variable:
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome=v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL);//final state
finishCompletion();
}
}
protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome=t;
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL);//final state
finishCompletion();
}
}
The read operation related to the outcome, that is, the get operation:
private V report(int s) throws ExecutionException {
Object x=outcome;
if (s==NORMAL)
return (V)x;
if (s >=CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
public V get() throws InterruptedException, ExecutionException {
int s=state;
if (s <=COMPLETING)
s=awaitDone(false, 0L);
return report(s);
}
public V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
if (unit==null)
throw new NullPointerException();
int s=state;
if (s <=COMPLETING &&
(s=awaitDone(true, unit.toNanos(timeout))) <=COMPLETING)
throw new TimeoutException();
return report(s);
}
Next we focus on these three methods: set(), get(), report()
We merge get() and report() together, and remove the redundant code, as follows:
public V get() {
int s=state;
if (s <=COMPLETING)
s=awaitDone(false, 0L);
Object x=outcome;
if (s==NORMAL);
return (V)x;
}
As can be seen from the above, when the state is NORMAL, the outcome is returned,
Look at the set() method again:
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome=v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL);//final state
finishCompletion();
}
}
In the second line, the state is changed from NEW to COMPLETING through the cas operation of UNSAFE.After the cas is set successfully, enter the if method, and then set the value for the outcome.In the fourth line, set the state of the state to the NORMAL state.As you can see from the remarks, this is a final state.From the NEW state to the NORMAL state, there is a fleeting state-COMPLETING.As you can see from the get method, if the state of the state is less than or equal to COMPLETING (that is, NEW state), that is, the current thread has not grabbed the execution time of the CPU, and enters the waiting state,
We put together the pseudocode for get() and set():
First you read the place labeled 4, and the value read is NORMAL, then the place labeled 3 must have been executed, because the state is modified by volatile, according to the happens-before relationship: The law of volatile variables: the write operation to the volatile field happens-before every subsequent read operation of the same field,
So we can conclude that the code labeled 3 is executed before the code labeled 4,
And according to the program order rules, namely:
In a thread, according to the order of control flow, the operations written in the front precede the operations written in the back.Note that the order of control flow is not the order of program code, because branching, Loops and other structures,
It can be concluded that: 2 happens-before 3 happens-before 4 happens-before 5;
According to the transitive rule, that is:
Transitivity: If A happens-before B, and B happens-before C, then A happens-before C
It can be concluded that 2 happens-before 5, and 2 is the write to the outcome variable, and 5 is the read of the outcome variable, so although the outcome variable is not volatile, it is modified by volatile The state variable, with the help of the happens-before relationship of the variable, completes the synchronous operation (that is, the write precedes the read),
0 Comments