Original statement: this article comes from the public number [fat pig learning programming], so easy and interesting programming in the form of cartoons, please note the source!

In our last article on the Three Sources of concurrent programming, we looked at the three sources of concurrent programming bugs: visibility, atomicity, and orderliness. With that in mind, let’s talk about how to solve these three problems.

A prelude to

Happens-before is what?

A happens-before B means that event A is visible to event B, regardless of whether event A and event B occur in the same thread. For example, if event A occurs on thread 1 and event B occurs on thread 2, the happens-before rule guarantees that event A is also seen on thread 2.

The role of the happens-before

Original statement: this article comes from the public number [fat pig learning programming], so easy and interesting programming in the form of cartoons, please note the source!

The happens-before principle is very important as it is the primary basis for determining whether threads are safe, and by relying on this principle we can address visibility and order issues in a concurrent environment. For example, if your boss asks you one day, “Does my concurrent code have thread safety issues?” you can look at them one by one according to the happens-before principle, and if they are atomic, you can shout “No problem!” Take this code for example:

i = 1; // thread A executes j = I; // Thread B executesCopy the code

Is j equal to 1? Assuming thread A’s happens-before thread B’s happens-before (j = I), then we can be sure that j = 1 is valid after thread B’s happens-before. If thread B does not have the happens-before principle, then j = 1 is not necessarily valid. That’s the power of the happens-before principle! Let’s go into its world!

Happens-before The eight principles solve problems of atomicity and orderliness

This rule means that in a thread, the previous action Happens Before any subsequent action, in order of procedure. This rule is easy to understand, after all, in a thread. You’ll think it’s a bad rule. In fact, this rule is a basic rule. Happens-before is a multi-threaded rule, so it has to be combined with other rules to make it sequential.

This rule means that writes to a Volatile variable are happens-before subsequent reads to that Volatile variable. As we mentioned in the previous article, because of caching, each thread has its own working memory, and if shared variables are not flushed into main memory in time, visibility problems can result, and thread B does not read thread A’s write in time. This problem can be avoided by adding Volatile, which acts as a way to flush changes to main memory immediately, bypassing the cache. Note, however, that in addition to ensuring availability, volatile can also prohibit specified reordering.

public class TestVolatile1 { private volatile static int count = 0; public static void main(String[] args) throws Exception { final TestVolatile1 test = new TestVolatile1(); Thread th1 = new Thread(() -> { count = 10; }); Threadth2 = new Thread(() -> {system.out. println("count=" + count); }); // Start two threads th1.start(); th2.start(); }}Copy the code

Rule 3: Transitivity rule

This rule means that if A happens-before B, and B happens-before C, then A happens-before C. And that makes sense. For example, writer and reader are two different threads that have the following operations:

int x = 0; volatile boolean v = false; public void writer() { x = 42; // (1) v = true; // (2)} public void reader() {if (v == true) {// (3)} (4)}}Copy the code

The difference between this example and the Volatile example is that there are two variables. So let’s analyze it: (1) and (2) in the same thread, according to rule 1, (1) happens-before (2) (3) and (4) in the same thread, likewise, (3) happens-before (4) According to rule 2, because v is volatile, Then (2) must happens-before (3). Then, according to the transitivity rule, (1) happens-before at (4), so x must be 42. So even if x is not volatile, it is guaranteed to be visible! So now you know why it’s interesting to see rule 1 combined with other rules.

Rule 4: Lock rules in pipe process

Unlocking in the fingering process must occur before subsequent locking. A pipe is a generic synchronization primitive, and synchronized is the implementation of pipe in Java. A lock in a pipe is implicitly implemented in Java. For example, in the following code, the lock is automatically locked before entering a synchronized block, and the lock is automatically released after executing the block.

Synchronized (this) {if (this.x < 10) {// critical section}} // synchronized (this) {// synchronized (this)Copy the code

In both single-threaded and multi-threaded environments, a lock must be unlocked before it can be locked.

Rule 5: Thread start rule

After main thread A starts child thread B (thread A calls thread B’s start() method), child thread B can see what the main thread did before it started child thread B.

private static long count = 0; Public static void main(String[] args) throws InterruptedException {Thread B = new Thread(() -> {// Before the main Thread calls B.start() All changes to shared variables are visible here // so count must be 10 system.out.println (count); }); // Change the shared variable count = 10; B.start(); }Copy the code

Rule 6: Thread termination rule

Thread A waits for thread B to complete (by calling join() on thread B). If thread A calls join() on thread B and returns successfully, the main thread can see the operation of the child thread. In other words, any operation in thread B happens-before returns from the join() operation.

private static long count = 0; Public static void main(String[] args) throws InterruptedException {Thread B = new Thread(() -> {// Before the main Thread calls B.start() All changes to shared variables are visible here // so count must be 10 count = 10; }); B.start(); // the main thread waits for the child thread to complete b.join (); // All changes made by child threads to shared variables are visible after the main thread calls b.join (); system.out.println (count); //count must be 10}Copy the code

Rule 7: Thread interrupt rule

A call to the threadinterrupt () method occurs first when the code of the interrupted thread detects the occurrence of an interrupt event. That is, Thread A calls the interrupt() method of Thread B, happens-before Thread A finds that Thread B has been interrupted by Thread A (thread.interrupted () checks if an interrupt has occurred).

private static long acount = 0; private static long bcount = 0; public static void main(String[] args) throws InterruptedException { Thread B = new Thread(() -> { bcount = 7; System.out.println(" bcount="+bcount+" acount="+acount); while (true){ if (Thread.currentThread().isInterrupted()){ bcount = 77; System.out.println(" bcount="+bcount+" acount="+acount); return; }}}); B.start(); Thread A = new Thread(() -> { acount = 10; Println (" bcount="+bcount+" acount="+acount); B.interrupt(); acount = 100; Println (" bcount="+bcount+" acount="+acount); }); A.start(); }Copy the code

Rule 8: Object rules

An object’s initialization happens — before the start of its finalize () method — when the constructor ends, usually with new. Finalize () is defined in java.lang.object, that is, every Object has a method. This method is called when the object is reclaimed. This principle emphasizes that the result of object initialization in multithreaded cases must be visible to any subsequent object destruction method.

Public HappensBefore8(){system.out.println (" constructor "); public HappensBefore8(){system.out.println (" constructor "); {Override protected void finalize() throws Throwable {system.out.println (" object destruction "); } public static void main(String[] args){ new HappensBefore8(); System.gc(); }Copy the code

All those questions about order

Original statement: this article comes from the public number [fat pig learning programming], so easy and interesting programming in the form of cartoons, please note the source!

The concept of extended orderliness: The natural orderliness of programs in the Java memory model can be summed up in a sentence, if observed within the thread, all operations are ordered; If you observe another thread in one thread, all operations are unordered. The first part of the sentence refers to the phenomenon of “instruction reordering” and “main-main-memory synchronization delay”. As-if-serial

The execution result of a single-threaded program cannot be changed, no matter how it is reordered. The compiler, runtime, and processor must comply with the AS-IF-Serial semantics. So the compiler and processor do not reorder operations that have data dependencies, because such reordering changes the execution result. However, if there are no data dependencies between the operations, they can be reordered by the compiler and processor.

Highlight: Ensure sequential execution in a single thread. Synchronized only one thread is running at a time, which guarantees orderliness. As for the double-check case, it wasn’t because synchronized didn’t guarantee order. It is the reordering of instructions that causes disorder in multiple threads.

conclusion

Original statement: this article comes from the public number [fat pig learning programming], so easy and interesting programming in the form of cartoons, please note the source! This article is reprinted from the public number [fat rolling pig learning programming] programming with comics so easy and interesting! Welcome to pay attention!