3, Communication between multiple threads

Posted by FUEL on Sun, 06 Mar 2022 15:32:33 +0100

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

Topics: Java Spring Distribution Concurrent Programming architecture