This article will cover:
- Java Thread Basic operations (create, wait, etc.)
- Java Thread Synchronization primitives (synchronous, mutex)
If you are familiar with the above topics, please stop.
Java Thread Basic Operations
The Java Threading API is provided in the Java.lang.Thread class, and the basic operation of the thread is encapsulated as a method of the thread class, which is commonly used in the following ways:
|
Method |
Description |
void |
Start () |
Start thread |
void |
Join () |
Wait for thread to end |
Create (START) thread
In Java, the process of creating a thread is divided into two steps:
- Create an executable (Runnable) thread object;
- Call it's start () method;
An executable thread object, which is a thread object that can invoke start (), and there are two ways to create an executable thread object:
- Inherit (extends) the thread class, overloading the Run () method;
- Implement the (implements) Runnable interface (implement the Run () method);
The code examples for two objects that create threads are as follows:
Inherit the thread class
Inherit the thread class to create threads, as follows:
Class Extendsthread extends Thread {@Overridepublic void Run () {for (int i = 0; i <; ++i) {System.out.print ("*"); tr y {thread.sleep;} catch (Interruptedexception e) {e.printstacktrace ();}}}} public class Testextendsthread {public static void main (string[] args) {//1. To create thread object threads Backthread = new Extendsthread () ; 2. Start thread Backthread.start (); for (int i=0; i <; ++i) {System.out.print ("#"); try {thread.sleep ()} catch (Interruptedexception e) {E.printstac Ktrace ();}}}
The program prints the * and # are alternating; This shows that the Backthread run () and the main thread are executing at the same time! Of course, if the code of a thread is not reused multiple times, the thread can be written in the form of an "anonymous inner 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);} catch (Interruptedexception e) {e.printstacktrace ();}}}}. Start (); for (int i = 0; i < ++i) {System.out.print ("#"); try {thread.sleep ()} catch (Interruptedexception e) {E . Printstacktrace ();}}}
Implementing the Runnable Interface
Another way to create a thread object in Java is to implement the Runnable interface, and then construct the thread with an instance of the specific class as a parameter of thread, as follows:
Class Runnableimpl implements Runnable {@Overridepublic void Run () {for (int i=0; i <; ++i) {System.out.print ("*"); t ry {thread.sleep;} 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) {System.out.print ("#"), try {thread.sleep (+);} catch (Interruptedexception e) {E.pri Ntstacktrace ();}}}
Similarly, if the Runnableimpl is not reused, it can also be written in the form of an "anonymous inner class":
public class Testimlementsrunnable {public static void main (string[] args) {new Thread (new Runnable () {@Overridepublic voi D run () {for (int i=0; i <; ++i) {System.out.print ("*"); try {thread.sleep ()} catch (Interruptedexception e) {E.P Rintstacktrace ();}}}). Start (); for (int i=0; i <; ++i) {System.out.print ("#"); try {thread.sleep ()} catch (Interruptedexception e) {E.printstac Ktrace ();}}}
Both of these methods implement the run () method, and the thread's start () method invokes the run () method of the incoming Runnable object (or calls its own Run method). The function of run () here is to provide a portal for the new thread, or run to describe what the new thread will do in the future, or the callback function of some C libraries.
Wait for thread to end
The thread's join () method provides the ability to "wait for the thread to end," and the main thread of Java waits for the end of another thread by default. Thread.Join () provides the ability for one thread to wait for another thread to function, for example, to call Backthread.join () in the Main method (the main thread), and the main thread will wait at the call until Backthread execution is complete. The following code is the typical order of start and join use:
In Main () Runnable r = new Runnable () {public void run () { //... }}; Thread back = new Thread (r); Back.start (); Back.join ();
The sequence diagram for this code corresponds to the following:
The function of start () is to start a thread (the program execution flow), which causes the execution process to split in two, while join () is the opposite of start, allowing two execution processes to "merge" as shown:
Two threads and several method execution time relations, the execution process first "two" and "one".
Mutually exclusive
The mutually exclusive semantics of Java are provided by the Synchronized keyword, in two specific ways:
- Synchronized code block
- Synchronized method
The following are described separately.
Why do we need mutual exclusion?
Since this article is positioned as a primer for multithreaded programming, let's introduce why there are mutex issues.
Guess the output of the following program:
public class Nonatomic {static int count = 0;public static void Main (string[] args) {thread-back = new Thread () {@Override public 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 does not output 20000 as expected, and is always smaller. Why is that? Because ++count, the operation is not "atomic", that is, it is not a command to complete the function. On most architectures, implementing an in-memory integer "self-increment" operation requires at least three steps:
- Read data from memory to registers
- Add one in the Register
- Write back to Memory
One possible two threads performing the "self-increment" scenario are as follows:
In this image, A and b two threads perform "self-increment" on value at the same time, and the expected value of value should be 11, while the actual value is 10.
Thus, to ensure that the "self-increment" operation of the multi-threaded environment is correct, it is necessary to ensure that the above three operations "one-time execution" without interference by other threads, this is called "atomicity".
Synchronized code block
The synchronized code block is in the following form:
Synchronized (obj) {//do something.}
This code guarantees the "atomicity" of the code within the curly braces, meaning that the two-thread execution of this block of code shows the "Do not execute, or execute All" feature, which is "mutually exclusive execution." Two synchronized code blocks that use the same obj also have an attribute of "mutually exclusive execution."
Just modify the above nonatomic slightly:
static int count = 0; Add a row after: Static object lock = new Object (); ++count to: Synchronized (lock) {++count;}
The output of the program is guaranteed to be 20000.
Synchronized method
Synchronized code blocks are usually part of a method, and if the entire method body is locked with synchronized, and the object is 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 means, this method:
Public synchronized void SomeMethod () {//do something ...}
Equivalent to:
public void SomeMethod () {synchronized (this) {//Do something ...}}
Synchronous
In layman's words, "Synchronization" is a time-series (sequential) relationship that guarantees two thread events, which is useful in a multithreaded environment. For example, two threads A, B is performing a series of work AI, Bi, now want to make the AP happen before BQ, you need to use "Synchronization primitives":
Calls that support synchronous operations are called synchronization primitives, which are typically defined as conditional variables (condition variable) in most of the operating system textbooks.
The Java synchronization primitives are several methods of the Java.lang.Object class:
- Wait () Waits for notification, and the call blocks the current thread.
- Notify () notifies that if more than one thread is blocked on the obj, the call wakes up one (blocking) thread waiting for the obj.
- Notifyall () notifies that if more than one thread is blocked on the obj, the call wakes up all (blocking) threads waiting for the obj.
Notify () is typically used to inform "available resources"; For example, in the producer-consumer model, when the buffer is empty, the consumer thread waits for the arrival of the new product, at which point the producer thread makes a product available notify () notifies the consumer thread.
Notifyall () is typically used to notify "state change", for example, in a multithreaded test program, when multiple background threads are created, all wait for the main thread to issue a "start Test" command, at which point the main thread can notify each test thread with Notifyall ().
For example, the following code simulates the starting process of an athlete: first, the starter waits for an athlete to be ready; then the starter is shot and all the runners start;
public class Teststartrunning {static final int num_athletes = ten; static int readycount = 0;static Object ready = new OBJ ECT (); static object start = new Object ();p ublic static void Main (string[] args) {thread[] athletes = new Thread[num_athlet es];//Create athlete for (int i = 0; i < athletes.length; ++i) {final int num = i;athletes[i] = new Thread () {@Overridepublic vo ID run () {System.out.println (Thread.CurrentThread (). GetName () + "ready!"); Synchronized (Ready) {++readycount;ready.notify ();//notify the starter, "I ' m ready!"} Wait for the starting pistol to ring try {synchronized (start) {start.wait ()}} catch (Interruptedexception e) {e.printstacktrace ()} System.out.println (Thread.CurrentThread (). GetName () + "go!");}}; Players play for (int i = 0; i < athletes.length; ++i) Athletes[i].start ();//main thread acts as referee role try {synchronized (Ready) {//wait for all athletes In position while (Readycount < athletes.length) {ready.wait ();}}} catch (Exception e) {e.printstacktrace ();} System.out.println (Thread.CurrentThread (). GetName () + "start!"); Synchronized (start) {StarT.notifyall (); The Starting Pistol}}}
Signal loss
Wait/notify/notifyall provides a way for inter-thread event notification, but this notification cannot be effectively "remembered", so there is a possibility of a notification loss (notify missing)-the line enters upgradeable notify to send the notification, Wait after the thread receiving the notification, at which point the prior notification is lost. The POSIX specification is called signal loss, since most operating systems (Linux,mac,unix) now follow POSIX, so the word "signal loss" is used more broadly.
Here is a demo notification of the Missing code:
public class Testnotifymissing {static object cond = new Object ();p ublic static void Main (string[] args) {new Thread () {PU Blic 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 does not 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".
In layman's words, wait/notify is just a verbal communication, and if you don't hear it, you'll miss it (unlike email, bulletin boards, you can get notifications later than others).
how to avoid the loss of notifications? since the notify itself does not have "memory", it is possible to use an additional variable as a "bulletin board", and to modify the "bulletin board" before notify, so that even if other threads call wait later than notify, you can see a notification written on the common board.
This also explains another language design problem: Why should Java's wait and notify end have to be locked with synchronized? first of all, this is not the grammar level of the rules, not so can be compiled through, but the runtime will throw exceptions, this is a runtime security check mechanism of the JVM, this mechanism is to remind us-should use additional variables to prevent the loss of notifications. For example, the notifymissing just needs a little modification to end it normally.
public class Testnotifymissingsolution {static Boolean notified = FALSE;//+++++static Object cond = new Object ();p ublic 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 example Testnotifymissingsolution, adding if (!notified) before cond.wait () also works fine, but this practice is associated with the while (...) given in the document. , the concept of false wakeup (spurious Wakeup) is also pointed out in the document. False wakeup in programming with POSIX Threads: When a thread wait on a condition variable, Broadcast (equivalent to notifyall) or signal (equivalent to notify) calls do not occur on this condition variable, and wait may also return. False wakeup sounds strange, but on multicore systems, making conditional wake fully predictable can cause most condition variable operations to slow down. "
To prevent false wakeup, you need to continue to check if a condition is reached after wait returns, and all the usual wait-side conditions are written as while instead of if, in Java, usually:
Wait thread: Synchronized (cond) {while (!done) {cond.wait ()}} Wake-Up Thread: Dosth (); synchronized (cond) {done = True;cond.notify ();}
Summarize
In the concept of < operating system >, a mutex (mutex) is provided for "mutually exclusive semantics", which provides the called conditional variable (Condition Variable) of the synchronization semantics. In Java, the Synchronized keyword and java.lang.Object provide the mutex (mutex) semantics, and the Java.lang.Object Wait/notify/notifyall provides the conditional variable semantics.
In addition, the recycling of objects in multi-threaded environments is very difficult, the Java Runtime Environment of garbage collection (garbage COLLECTION,GC) function to alleviate the burden of programmers.
Reference
Java 1.6 Apidocs thread,http://tool.oschina.net/uploads/apidocs/jdk-zh/java/lang/thread.html
Java Concurrency in practice ("Java Concurrency Practice" in Chinese translation)
Spurious Wakeup--wikipedia,http://en.wikipedia.org/wiki/spurious_wakeup
Discussion of conditional variables and false wake (spurious wakeup) in multithreaded programming, http://siwind.iteye.com/blog/1469216
Java Concurrency Primitives-threads, mutexes, and synchronizations