Creating Threads
A thread represents a separate execution flow that has its own program execution counter and has its own stack. Below, we create a thread to establish a visual sense of threads, in Java to create a thread in two ways, one is to inherit the thread, and the other is to implement the Runnable interface, we first look at the first kind.
Inherit thread
In Java Java.lang.Thread This class represents a thread, a class can inherit the thread and override its run method to implement a thread as follows:
public class Hellothread extends Thread {
@Override
public void Run () {
System.out.println ("Hello");
}
}
Hellothread This class inherits the thread and overrides the Run method. The method signature of the Run method is fixed, public, has no parameters, has no return value, and cannot throw a inspected exception. The Run method is similar to the main method in a single-threaded program, and the thread starts execution until the end of the first statement of the Run method.
Defining this class does not mean that the code will start executing, the thread needs to be started, the boot needs to create a Hellothread object first, and then call the thread's Start method, as follows:
public static void Main (string[] args) {
Thread thread = new Hellothread ();
Thread.Start ();
}
We created a thread object in the main method and called its Start method, and after calling the Start method, the Hellothread run method begins execution and the screen output:
Hello
Why is the call start and execute the Run method? Start means that the thread is started, making it a separate execution stream, behind which the operating system allocates thread-related resources, each thread has a separate program execution counter and stack, and the operating system dispatches the thread as a separate individual, allocating time slices for execution, and starting with the Run method.
What if you don't call start and call the Run method directly? The output of the screen does not change, but it does not start a separate execution stream, the code of the Run method is still executed in the main thread, and the Run method is just a common method of the main method invocation.
How do you confirm which thread the code is executing on? Thread has a static method, CurrentThread, that returns the currently executing thread object:
public static native Thread CurrentThread ();
Each thread has an ID and name:
Public long GetId ()
Public final String GetName ()
In this way, we can tell which thread the code is executing in, and we add some code to the Hellothead Run method:
@Override
public void Run () {
SYSTEM.OUT.PRINTLN ("Thread Name:" + Thread.CurrentThread (). GetName ());
System.out.println ("Hello");
}
If you start the thread in the main method through the Start method, the program output is:
Thread name:thread-0
Hello
If you call the Run method directly in the main method, the program output is:
Thread Name:main
Hello
After calling start, there are two execution streams, a new one executes the run method, the old one continues the main method, two execution streams execute concurrently, the operating system is responsible for scheduling, on a single CPU machine, only one thread is executing at the same time, on a multi-CPU machine, Multiple threads can execute simultaneously at the same time, but the operating system shields us from the difference, giving the programmer the feeling that multiple threads are executing concurrently, but which one executes after which is not necessarily. When all threads have finished executing, the program exits.
Implementing the Runnable Interface
Although it is simpler to implement threads by inheriting thread, we know that Java only supports single inheritance, and that each class can have at most one parent class, and if the class already has a parent class, it can no longer inherit the thread, which is achieved by implementing the Java.lang.Runnable interface.
The definition of the Runnable interface is simple, with only one run method, as follows:
Public interface Runnable {
public abstract void run ();
}
A class can implement the interface and implement the Run method, as follows:
public class Hellorunnable implements Runnable {
@Override
public void Run () {
System.out.println ("Hello");
}
}
It is not enough to just implement runnable, to start a thread, or to create a thread object, but to pass a Runnable object as follows:
public static void Main (string[] args) {
Thread hellothread = new Thread (new hellorunnable ());
Hellothread.start ();
}
Whether you implement a thread by inheriting thead or implementing the Runnable interface, the startup thread is the Start method that invokes the thread object.
Basic properties and methods of threads
ID and name
As we mentioned earlier, each thread has an ID and Name,id is an incrementing integer, plus one for each thread created, and the default value for name is "thread-" followed by a number, name can be specified in the thread's constructor method, can also be set by the SetName method, give thread a friendly name, you can easily debug.
Priority level
Threads have a concept of precedence, in Java, with a priority of 1 to 10, and a default of 5, the related method is:
Public final void setpriority (int newpriority)
Public final int getpriority ()
This priority is mapped to the priority of the thread in the operating system, however, because the operating system is different, not necessarily 10 priority, the difference of priority in Java may be mapped to the same priority in the operating system, in addition, the priority of the operating system is more of a recommendation and hint, rather than coercion, Simply put, in programming, do not rely too much on priority.
State
Threads have a state concept, and thread has a way to get the state of a thread:
Public State getState ()
The return value type is thread.state, which is an enumeration type with the following values:
public enum State {
NEW,
RUNNABLE,
BLOCKED,
Waiting,
Timed_waiting,
TERMINATED;
}
For these states, let's simply explain the following:
NEW: The thread state without calling start is new
TERMINATED: The thread is running at the end of a state of TERMINATED
RUNNABLE: After calling start the thread executes the Run method without blocking when the state is RUNNABLE, however, RUNNABLE does not mean that the CPU must be executing the thread's code, may be executing or may be waiting for the operating system to allocate time slices, but it is not waiting for other conditions
BLOCKED, Waiting, timed_waiting: both indicate that the thread is blocked, waiting for some conditions, the difference between which we re-introduce in subsequent chapters
Thread also has a method that returns whether the thread is alive:
Public Final native Boolean isAlive ()
After the thread is started, the return value is true before the Run method finishes running.
Whether to Daemo threads
The thread has a property of whether the thread is Daemo, and the related methods are:
Public final void Setdaemon (Boolean on)
Public Final Boolean Isdaemon ()
As we mentioned earlier, the startup thread starts a separate execution flow, and the whole program exits only when all the threads are finished, but the Daemo thread is the exception, and the program exits when the entire program is left with the Daemo thread.
What is the use of Daemo threads? It is generally a secondary thread for other threads, and it has no meaning when the main thread that it assists exits. When we run a program of even the simplest "Hello World" type, in fact, Java also creates multiple threads, in addition to the main thread, there is at least one thread responsible for the garbage collection, the thread is the Daemo thread, at the end of the main thread, The garbage collection thread will also exit.
Sleep method
Thread has a static sleep method that calls the method that causes the current thread to sleep for a specified time in milliseconds:
public static native void sleep (long Millis) throws interruptedexception;
During sleep, the thread gives up the CPU, but the time of sleep is not necessarily the exact number of milliseconds given, there may be some deviation, and the deviation is related to the accuracy and precision of the system timer and the operating system scheduler.
During sleep, threads can be interrupted, and if interrupted, sleep throws interruptedexception, interrupts, and interrupt handling, which we'll cover later in this chapter.
Yield method
Thread also has a way to give up the CPU:
public static native void yield ();
This is also a static method, called the method, is to tell the operating system scheduler, I am not anxious to occupy the CPU, you can first let other threads run. However, this is only a recommendation to the scheduler, how the scheduler handles it is not necessarily, it may completely ignore the call.
Join method
In the previous Hellothread example, Hellothread did not finish, the main thread might be executed, thread has a join method that lets the thread calling join wait for the thread to end, and the Join method declares:
Public final void Join () throws Interruptedexception
In the process of waiting for the thread to end, the wait may be interrupted and thrown interruptedexception if interrupted.
The Join method also has a variant that can limit the maximum time to wait, in milliseconds, and, if 0, to wait for no time:
Public final synchronized void join (long Millis) throws Interruptedexception
In the previous Hellothread example, if you want the main thread to exit after the child thread ends, the main method can be changed to:
public static void Main (string[] args) throws Interruptedexception {
Thread thread = new Hellothread ();
Thread.Start ();
Thread.Join ();
}
Obsolete methods
There are also methods in the thread class that appear to control the life cycle of the thread, such as:
Public final void Stop ()
Public final void Suspend ()
Public final void Resume ()
These methods have been flagged for various reasons in order to be obsolete, and we should not use them in our programs.
Shared Memory and issues
Shared memory
As we mentioned earlier, each thread represents a separate execution flow, has its own program counter, has its own stack, but the threads can share memory, and they can access and manipulate the same objects. Let's look at an example with the following code:
public class Sharememorydemo {
private static int shared = 0;
private static void incrshared () {
Shared + +;
}
Static Class Childthread extends Thread {
List<string> list;
Public Childthread (list<string> List) {
This.list = list;
}
@Override
public void Run () {
Incrshared ();
List.add (Thread.CurrentThread (). GetName ());
}
}
public static void Main (string[] args) throws Interruptedexception {
list<string> list = new arraylist<string> ();
Thread T1 = new Childthread (list);
Thread t2 = new Childthread (list);
T1.start ();
T2.start ();
T1.join ();
T2.join ();
SYSTEM.OUT.PRINTLN (shared);
SYSTEM.OUT.PRINTLN (list);
}
}
In the code, a static variable shared and a static inner class childthread are defined, and in the Main method, two Childthread objects are created and started, and the same list object is passed. The Childthread Run method accesses the shared variable, and the share and List,main methods finally output the shared share and list values, which in most cases output the desired value:
2
[Thread-0, Thread-1]
With this example, we want to highlight the relationship between execution flow, memory, and program code.
In this example, there are three execution flows, one executes the main method, and the other two executes the Childthread run method.
Different execution streams can access and manipulate the same variables, such as the shared and list variables in this example.
Different execution streams can execute the same program code, as in this example incrshared method, Childthread Run method, executed by two childthread execution flow, incrshared method is defined externally, However, it is important to understand which thread executes the code when parsing the execution of the code, which is performed by the Childthread execution flow.
When multiple execution flows execute the same program code, each execution flow has a separate stack, and the parameters and local variables in the method have their own copy.
When multiple execution flows can manipulate the same variables, there may be some unexpected results that we'll look at.
Race condition
The so-called race condition (race condition) means that when multiple threads access and manipulate the same object, the final execution result is related to the execution timing, which may or may not be correct, let's look at an example:
public class Counterthread extends Thread {
private static int counter = 0;
@Override
public void Run () {
try {
Thread.Sleep ((int) (Math.random () *100));
} catch (Interruptedexception e) {
}
Counter + +;
}
public static void Main (string[] args) throws Interruptedexception {
int num = 1000;
thread[] threads = new Thread[num];
for (int i=0; i<num; i++) {
Threads[i] = new Counterthread ();
Threads[i].start ();
}
for (int i=0; i<num; i++) {
Threads[i].join ();
}
SYSTEM.OUT.PRINTLN (counter);
}
}
This code is easy to understand, there is a shared static variable counter, the initial value is 0, in the main method created 1000 threads, each thread is a random sleep, and then to counter plus 1,main thread waits for all threads to end after the output counter value.
The expected result is 1000, but the actual execution, found that each output of the results are different, generally not 1000, often more than 900. Why is that? Because counter++ is not an atomic operation, it is divided into three steps:
Fetch the current value of the counter
Add 1 based on current value
Re-assign the new value to counter
Two threads may perform the first step at the same time, take the same counter value, for example, take 100, the first thread finishes executing counter becomes 101, and the second thread finishes after 101, the final result is inconsistent with expectations.
How to solve this problem? There are several ways:
For these methods, we will introduce them in the following chapters.
Memory visibility
Multiple threads can share access and manipulate the same variables, but one thread changes a shared variable, and the other thread doesn't have to see it right away, or even never see it, which may be counterintuitive, let's look at an example.
public class Visibilitydemo {
private static Boolean shutdown = FALSE;
Static Class Hellothread extends Thread {
@Override
public void Run () {
while (!shutdown) {
Do nothing
}
System.out.println ("Exit Hello");
}
}
public static void Main (string[] args) throws Interruptedexception {
New Hellothread (). Start ();
Thread.Sleep (1000);
shutdown = true;
SYSTEM.OUT.PRINTLN ("Exit main");
}
}
In this program, there is a shared Boolean variable shutdown, which initially false,hellothread in the case that shutdown is not true, and when shutdown is true exits and outputs "exit Hello", The main thread starts Hellothread and then sleeps for a while, then sets shutdown to true and finally outputs "exit main".
The expected result is that all two threads exit, but actually executes, it is likely to be found that Hellothread never exits, that is, in the Hellothread execution flow, shutdown is always false, even if the main thread has changed to true.
What's going on here? This is the memory visibility issue. In a computer system, in addition to memory, the data is also cached in the CPU registers and levels of cache, when accessing a variable may be directly from the register or CPU cache, not necessarily in memory to fetch, when modifying a variable, it may also be written to the cache, and later will be updated in memory synchronously. In a single-threaded program, this is generally not a problem, but in multithreaded programs, especially with multiple CPUs, this is a serious problem. One thread changes memory, another thread does not see it, one is that the modification is not synchronized to memory in time, and the other thread does not read from memory at all.
How to solve this problem? There are several ways:
For these methods, we will introduce them in the following chapters.
Advantages and costs of threading
Advantages
Why create a separate execution flow? Or what are the advantages of threading? There are at least the following points:
Full use of multi-CPU computing power, single-threaded only one CPU, using multithreading can take advantage of multi-CPU computing power.
Full use of hardware resources, the CPU and hard disk, the network can work simultaneously, one thread while waiting for network IO, another thread can fully utilize the CPU, for multiple independent network requests, can use multiple threads simultaneously request.
In a user interface (GUI) application, maintaining the responsiveness of the program, the interface and background tasks are usually different threads, otherwise, if everything is a thread to execute, when a slow task is performed, the entire interface stops responding and cannot cancel the task.
Simplifying modeling and IO processing, for example, in a server application, using a separate thread for each user request, it is much easier to model and write programs than to use a single thread to process various requests from various users, as well as various network and file IO events.
Cost
With regard to threads, we need to know that it is cost-aware. Creating threads consumes the resources of the operating system, and the operating system creates the necessary data structures, stacks, program counters, and so on for each thread, and it takes time to create them.
In addition, thread scheduling and switching is also a cost, when there is equivalent to run the thread, the operating system will be busy scheduling, for a thread for a period of time, after the execution of another thread to execute, after a thread is switched out, the operating system needs to save its current context state to memory, The context state includes the current CPU register value, the value of the program counter, and so on, and after a thread is switched back, the operating system needs to restore its original context state, the whole process is called context switching, which is not only time consuming, but also invalidates many caches in the CPU, which is a cost.
Of course, these costs are relatively relative, and these costs are acceptable if the threads actually do a lot of things, but if you just perform the counter++ in the example in this section, that would be too expensive.
In addition, if the tasks performed are CPU intensive, that is, the CPU is mostly consumed, it is unnecessary to create a thread that exceeds the number of CPUs and does not speed up the execution of the program.
Summary
In this section, we introduce some basic concepts of Java thread, including how to create threads, some basic properties and methods of threads, multiple threads can share memory, but there are two important problems with shared memory, one is race condition, the other is memory visibility, and finally, we discuss some advantages and costs of threading.
For two issues with shared memory, in the next section, we discuss a solution for Java-the Synchronized keyword.
Basic concepts of Threading/Thinking logic of computer programs