preface
1, synchronized and Volatile
- volatile
- Visibility can be guaranteed, but atomicity cannot be guaranteed (thread safety problem)
- Prohibit reordering
- synchronized
- Both visibility and atomicity can be guaranteed
- Reordering is not prohibited
- synchronized is blocking. Only one thread can access it at a time
2, Reorder
- Concept: the cpu will optimize the code implementation and will not reorder the dependent code
- The execution order of the code may change
- However, the results of the implementation will not change
- Data dependency:
- Only for instruction sequences executed in a single processor and operations executed in a single thread
- Data dependencies between different processors and between different threads are not considered by compilers and processors
- Case:
- a and b are independent
- c and a, c and b have dependencies
int a=1; int b=2; int c = a * b;
3, As if serial
- Concept:
- Reorder anyway (compiler and processor to improve parallelism)
- The execution result of a (single threaded) program cannot be changed
- Compiler, runtime and processor must comply with as if serial semantics
- Conclusion:
- The problem of reordering does not exist in a single thread
- Reordering only needs to be considered in the case of multithreading
4, Application of volatile in reordering
- Assume that the following writer and reader are two independent threads
- If a and flag are not decorated with volatile, there is no dependency between a and flag
- The cpu may reorder the code in the writer
- First execute flag = true;, Then execute a = 1;
- This will cause the i result in the reader to be 0, which is contrary to the actual requirements
- Modifying a and flag with volatile can prohibit reordering and ensure the accuracy of running in multithreading
class ReorderExample { volatile int a = 0; volatile boolean flag = false; // Write thread public void writer() { a = 1; // 1 flag = true; // 2 } // 1.1 lines of code and 2 lines of code have no dependencies // Read thread public void reader() { if (flag) { // 3 int i = a * a; // 4 0 } } }
5, wait and notify
- wait: pause the currently executing thread and release the resource lock so that other threads can have a chance to run
- notify/notifyall: wake up the thread in the lock pool and make it run
- Requirements for use of wait and notify
- Be sure to execute in synchronized
- Be sure to hold the same lock
// Shared object class Res { // full name public String name; // Gender public String sex; // If it is true, it is allowed to read but not write // If it is false, it is allowed to write but not read. public boolean flag = false; } // Producer thread class IntThread extends Thread { public Res res; public IntThread(Res res) { this.res = res; } @Override public void run() { int count = 0; // 1 while (true) { synchronized (res) { if (res.flag) { try { res.wait();// Release the current lock object } catch (Exception e) { // TODO: handle exception } } if (count == 0) { res.name = "Xiao Hong"; res.sex = "female"; } else { res.name = "Xiaojun"; res.sex = "male"; } count = (count + 1) % 2;// 0 1 0 1 0 1 res.flag = true;// Mark the current thread as waiting res.notify();// Wake up the waiting thread } } } } // Consumer thread class OutThread extends Thread { public Res res; public OutThread(Res res) { this.res = res; } @Override public void run() { while (true) { synchronized (res) { try { if (!res.flag) { res.wait(); } Thread.sleep(1000); } catch (Exception e) { // TODO: handle exception } System.out.println(res.name + "," + res.sex); res.flag = false; res.notify(); } } } } public class App { public static void main(String[] args) { Res res = new Res(); IntThread intThread = new IntThread(res); OutThread outThread = new OutThread(res); intThread.start(); outThread.start(); } }
Test results: the production thread produces a res and the consumption thread consumes a res