Java concurrency primitive-thread, mutex and synchronization, java concurrency primitive mutex
This article will introduce:
- Basic Java thread operations (creation, waiting, etc)
- Java thread synchronization primitive (synchronous and mutually exclusive)
If you are familiar with the above topics, stop.
Basic Java thread operations
The Java Thread API is provided by the java. lang. Thread class. The basic operations of a Thread are encapsulated as methods of the Thread class. The common methods are as follows:
|
Method |
Description |
Void |
Start () |
Start thread |
Void |
Join () |
Wait until the thread ends |
Create (start) thread
In Java, the process of creating a thread is divided into two steps:
An executable thread object is a thread object that can be started by calling start (). There are two ways to create an executable thread object:
The code examples of the two types of thread creation objects are as follows:
Inherit Thread class
Inherit the Thread class creation Thread, as follows:
Class ExtendsThread extends Thread {@ Overridepublic void run () {for (int I = 0; I <100; ++ I) {System. out. print ("*"); try {Thread. sleep (100);} catch (InterruptedException e) {e. printStackTrace () ;}}} public class TestExtendsThread {public static void main (String [] args) {// 1. create Thread object Thread backThread = new ExtendsThread (); // 2. start thread backThread. start (); for (int I = 0; I <100; ++ I) {System. out. print ("#"); try {Thread. sleep (100);} catch (InterruptedException e) {e. printStackTrace ();}}}}
The * and # printed by this program are alternating. This shows that the backThread's run () and the main thread are running at the same time! Of course, if the code of a thread is not repeatedly used, you can write the thread as an "anonymous internal class:
public class TestExtendsThread {public static void main(String[] args) {new Thread() {public void run() {for (int i = 0; i < 100; ++i) {System.out.print("*");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}}.start();for (int i = 0; i < 100; ++i) {System.out.print("#");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}}
Implement the Runnable interface
Another way to create a Thread object in Java is to implement the Runnable interface and use the instance of a specific class as the Thread parameter to construct the Thread. The Code is as follows:
Class RunnableImpl implements Runnable {@ Overridepublic void run () {for (int I = 0; I <100; ++ I) {System. out. print ("*"); try {Thread. sleep (100);} catch (InterruptedException e) {e. printStackTrace () ;}}} public class TestImlementsRunnable {public static void main (String [] args) {Runnable callback = new RunnableImpl (); Thread backThread = new Thread (callback ); backThread. start (); // start thread for (int I = 0; I <100; ++ I) {System. out. print ("#"); try {Thread. sleep (100);} catch (InterruptedException e) {e. printStackTrace ();}}}}
Similarly, if RunnableImpl is not reused, it can also be written as an "anonymous internal class:
public class TestImlementsRunnable {public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {for(int i=0; i < 100; ++i) {System.out.print("*");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}}).start(); for(int i=0; i < 100; ++i) {System.out.print("#");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}}
Both methods implement the run () method, while the start () method of Thread calls the run () method of the passed Runnable object (or calls its own run method ). Run () is used to provide an entry for a new thread, or run describes what the new thread will "do" in the future. It is equivalent to some C-library callback functions.
Wait until the thread ends
The join () method of the Thread provides the "wait for the end of the Thread" function. By default, the main Thread of Java will wait for the end of other threads. Thread. join () provides the function of waiting for another thread. For example, the backThread is called in the main method (main thread. join (); the main thread will wait at the call until the backThread is executed. The following code uses the following typical sequence of start and join operations:
// in main()Runnable r = new Runnable() { public void run() { // ... }};Thread back = new Thread(r);back.start();back.join();
The sequence diagram of the Code is as follows:
The role of start () is to start a thread (Program Execution stream), so that the execution process at the call is divided into two parts; while join () is opposite to start, merge the two execution flows into one, as shown in:
The two threads are successively related to the execution time of several methods. The execution process is divided into two parts and merged into one ".
Mutual Exclusion
The mutex semantics of Java is provided by the synchronized keyword. There are two types of mutex semantics:
The following sections describe each other.
Why do we need mutual exclusion?
As this article is positioned as a multi-threaded programming entry, I will explain why there is a mutex problem by the way.
Guess the output of the following program:
public class NonAtomic {static int count = 0;public static void main(String[] args) {Thread back = new Thread() {@Overridepublic void run() {for(int i=0; i<10000; ++i) {++count;}}};back.start();for(int i=0; i<10000; ++i) {++count;}try {back.join(); // wait for back thread finish.} catch (InterruptedException e) {e.printStackTrace();}System.out.println(count);}}
This program cannot output 20000 as imagined, but it is always smaller. Why? Because the + + count; operation is not "Atomic", that is, it is not a function that can be completed by a command. In most architectures, it takes at least three steps to implement the integer "auto-increment" Operation in the memory: one possible scenario where two threads simultaneously execute "auto-increment" is as follows:
In this figure, the threads A and B execute "auto-increment" on the value at the same time. The expected value should be 11, but the actual value is 10.
It can be seen that to ensure the correctness of the "auto-increment" operation in a multi-threaded environment, the above three operations must be "executed at one time" without being disturbed by other threads, this is the so-called "atomicity ".
Synchronized code block
The synchronized code block is in the following format:
synchronized(obj) {// do something.}
This Code ensures the "atomicity" of the Code in curly brackets. That is to say, when two threads execute this code block at the same time, it will show the "neither execution nor all execution" feature, that is, "mutex execution ". Two synchronized code blocks using the same obj also have the "mutex execution" feature.
You only need to slightly modify the NonAtomic above:
// Static int count = 0; add a row after: static Object lock = new Object (); // + + count to: synchronized (lock) {++ count ;}
To ensure that the output of the program is 20000.
Synchronized Method
The synchronized code block is usually part of the method. If the entire method body is locked with synchronized and the object this, if the entire method body needs to be locked with synchronized (this, you can also use the synchronized keyword to modify this method.
That is to say, this method:
public synchronized void someMethod() {// do something...}
It is equivalent:
public void someMethod() {synchronized(this) {// do something...}}
Synchronization
In layman's terms, "synchronization" is to ensure the timing (successive) Relationship of two thread events, which is very useful in a multi-threaded environment. For example, two threads A and B are executing A series of work Ai and Bi. To make the Ap happen before Bq, we need to use the "synchronization primitive ":
Calls that support "synchronization" operations are called "synchronization primitives". In most operating system textbooks, such primitives are usually defined as condition variables ).
The Java synchronization primitive is the java. lang. Object Class several methods:
Y () is usually used to notify "available resources". For example, when the buffer is empty in the producer-consumer model, the consumer thread waits for the arrival of a new product, after the producer thread produces a product, notify () can be used to notify the consumer thread.
Policyall () is usually used to notify "state change". For example, in a multi-threaded testing program, after multiple background threads are created, they all wait for the main thread to issue the "Start test" command, in this case, the main thread can notify each test thread of policyall.
For example, the following code is used to simulate the Starting Process of an athlete: first, the dispatcher waits for an athlete to be ready, and then the dispatcher starts with a gun;
Public class TestStartRunning {static final int NUM_ATHLETES = 10; static int readyCount = 0; static Object ready = new Object (); static Object start = new Object (); public static void main (String [] args) {Thread [] athletes = new Thread [NUM_ATHLETES]; // create an athlete for (int I = 0; I <athletes. length; ++ I) {final int num = I; athletes [I] = new Thread () {@ Overridepublic void run () {System. out. println (Thread. cu RrentThread (). getName () + "ready! "); Synchronized (ready) {++ readyCount; ready. Y (); // notify the dispatcher," I'm ready !"} // Wait for the starting gun to ring try {synchronized (start) {start. wait () ;}} catch (InterruptedException e) {e. printStackTrace ();} System. out. println (Thread. currentThread (). getName () + "go! ") ;};}// Enter the player for (int I = 0; I <athletes. length; ++ I) athletes [I]. start (); // The main thread acts as the referee role try {synchronized (ready) {// wait for all athletes to be in place while (readyCount <athletes. length) {ready. wait () ;}} catch (Exception e) {e. printStackTrace ();} System. out. println (Thread. currentThread (). getName () + "START! "); Synchronized (start) {start. policyall (); // start the starting gun }}}
Signal loss
Wait/notify/notifyAll provides a way to notify inter-thread events, but such notifications cannot be effectively "remembered". Therefore, there is a notification loss (notify missing) possible: the thread that sends the notification must notify y first, and then wait after receiving the notification. In this case, the advance notification will be lost. In POSIX specifications, it is called signal loss. Because most operating systems (LINUX, Mac, and Unix) currently follow POSIX, the term "signal loss" is more widely used.
The following code demonstrates the loss of notification:
public class TestNotifyMissing {static Object cond = new Object();public static void main(String[] args) {new Thread() {public void run() {try {Thread.sleep(1000); System.out.println("[back] wait for notify...");synchronized (cond) {cond.wait();}} catch (InterruptedException e) {e.printStackTrace();}System.out.println("[back] wakeup");}}.start();System.out.println("[main] notify");synchronized (cond) {cond.notify();}}}
This program cannot exit normally. The background thread has been waiting in the background because it missed the notification from the main thread, and the program will not output "[back] wake up ".
In layman's terms, wait/notify is just a verbal communication. If you don't hear it, you will miss it (unlike emails or bulletin boards, you can receive the notification later than the time someone else sends it ).
How can we avoid notification loss?Because notify does not have a "Memory", you can use additional variables as the "announcement board"; modify this "announcement board" before notify; in this way, even if the time for other threads to call wait is later than notify, you can see the notifications written on the "Public Board.
This also explains another language design problem:Why must the Java wait and ipvy be locked with synchronized?First, this is not a syntax-level rule. It can be compiled and passed without such writing, but an exception is thrown during runtime. This is a runtime security check mechanism of JVM, this mechanism reminds us that extra variables should be used to prevent loss of notifications. For example, just a slight modification of policymissing can be completed.
public class TestNotifyMissingSolution {static boolean notified = false; // +++++static Object cond = new Object();public static void main(String[] args) {new Thread() {public void run() {try {Thread.sleep(1000);System.out.println("[back] wait for notify...");synchronized (cond) {while(!notified) // +++++cond.wait();}} catch (InterruptedException e) {e.printStackTrace();}System.out.println("[back] wakeup");}}.start();System.out.println("[main] notify");synchronized (cond) {notified = true; // +++++cond.notify();}System.out.println("[main] notified");}}
False Wakeup
In the example testpolicymissingsolution, add if (! Notified), can also run normally; however, unlike the while (...) provided in the document, this document also points out the concept of Spurious Wakeup. The explanation of false wakeup in Programming with POSIX Threads is: when a thread wait is in a condition variable, broadcast (equivalent to policyall) does not occur in this condition variable) or signal (equivalent to policy) Call, wait may also return. False wake-up sounds strange, but on a multi-core system, making conditional wake-up completely predictable may lead to a slowdown in most conditional variable operations. "
To prevent false wakeup, you need to check whether a condition is fulfilled after wait returns. All conditions on the wait end are written as while instead of if. in Java, the conditions are usually:
// Waiting thread: synchronized (cond) {while (! Done) {cond. wait () ;}// wake up thread: dosomething (); synchronized (cond) {done = true; cond. Y ();}
Summary
In the <operating system> concept, Mutex provides the Mutex syntax and conditional Variable ). In Java, the synchronized keyword and java. lang. Object provide mutex semantics, while wait/notify/policyall of java. lang. Object provides conditional variable semantics.
In addition, it is very difficult to recycle objects in a multi-threaded environment. The Garbage Collection (GC) function in the Java Runtime Environment reduces the burden on programmers.
Reference
Java 1.6 apidocs Thread, http://tool.oschina.net/uploads/apidocs/jdk-zh/java/lang/Thread.html
Java Concurrency in Practice
Spurious Wakeup -- Wikipedia, http://en.wikipedia.org/wiki/Spurious_wakeup
Discussion of condition variables and false wakeup in multi-thread programming, http://siwind.iteye.com/blog/1469216