Three Ways to Create Threads in Java and Thread Control

Posted by sbarros on Sun, 26 May 2019 21:22:44 +0200

I. Processes and Threads

1. What is a process?

The process is a running program, and has some independent functions. The process is an independent unit for resource allocation and scheduling of the system.

2. Three Characteristics of Process

(1) Independence. Process is an independent entity in the system. It can have its own independent resources, and each process has its own private address space. Without the permission of the process itself, a user process cannot directly access the address space of other processes.
(2) Dynamics. The difference between a process and a program is that a program is only a static set of instructions, while a process is an active set of instructions in the system, which adds the concept of time to the process. Processes have their own life cycles and different states, which are concepts that are not available in programs.
(3) Concurrency. Multiple processes can be executed concurrently on a single processor without interacting with each other.

The understanding of operating system multi-process support: program instructions are executed through the cpu, only one program instructions are executed at a certain point in time, but the speed of executing instructions by the CPU is very fast, so the speed of switching execution of multiple program instructions on the CPU in turn is also very fast, so macroscopically it feels that multiple programs are executed concurrently. We can understand the concurrency of programs, macro concurrent execution and micro sequential execution.

3. What is a thread?

Threads are part of a process. A process can have multiple threads, and a thread must have a parent process. Threads can have their own stack, their own program counters and their own local variables, but they do not have system resources. They share all the resources of the process with other threads of the parent process.

Two. Three ways to create threads

1. Inheriting the Thread class to create threads

The steps are as follows:
A. Define a subclass of the Thread class and override the run() method of that class. The body of the run() method represents the task that the thread needs to complete.
b. Create an instance of Thread subclass, that is, create thread objects.
c. Call the start() method of the thread object to create and start the thread.

public class FirstThread extends Thread{
    private int i;
    
    //Rewrite the run() method. The body of the run() method is the execution of the thread.
    public void run(){
        for(;i<100;i++){
            //  When the thread class inherits the Thread class, you can get the current thread directly by using this
            //The getName() method of the Thread object returns the name of the current thread
            //So you can call the getName() method directly to return the name of the current thread
            System.out.println(getName()+" "+i);
        }
    }

    public static void main(String[] args) {
        for(int i=0;i<100;i++){
            //Call the current Thread () method of the Thread class to get the current thread
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==20){
                //Create and start the first thread
                new FirstThread().start();
                //Create and start a second thread
                new FirstThread().start();
            }
        }
    }
}

2. Implementing Runnable Interface to Create Thread Classes

a. Define the implementation class of the Runnable interface and override the run() method of the interface. The run() method body is the thread execution body of the thread.
b. Create an instance of the Runnable implementation class and use it as the target of the Thread class to create the Thread object, which is the real thread object.

public class SecondThread implements Runnable{
    private int i;
    
    //The run() method is also the executor of the thread
    @Override
    public void run(){
        for(;i<100;i++){
            //When a thread implements the Runnable interface
            //If you want to get the current thread, you can only use the Thread.currentThread() method
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
    public static void main(String[] args) {
        for(int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==20){
                SecondThread st=new SecondThread();
                //Create new threads through the new Thread(target,name) method
                new Thread(st, "New thread 1").start();
                new Thread(st, "New thread 2").start();
            }
        }
    }
}

3. Create threads using Callable and Future

A. Create an implementation class of the Callable interface and implement the call() method, which acts as the thread's executor, and the call() method has a return value, then create an instance of the Callable implementation class.
b. Use the FutureTask class to wrap the Callable object, which encapsulates the return value of the Callable object's call() method.
c. Create and start new threads using the FutureTask object as the target of the Thread object.
d. Use the get() method of the FutureTask object to get the return value after the execution of the sub-thread.

public class ThridThread {
    public static void main(String[] args) {
        //Create Callable Objects
        ThridThread rt=new ThridThread();
        //Create Callable < Integer > objects using Lambda expressions first
        //Use FutureTask to wrap Callable objects
        FutureTask<Integer> task=new FutureTask<>((Callable<Integer>)()->{
            int i=0;
            for(;i<100;i++){
                System.out.println(Thread.currentThread().getName()+" "+i);
            }
            //Return value of call() method
            return i;
        });
        for(int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+"Loop variable i Value: "+i);
            if(i==20){
                //The essence is to create and start threads with Callable objects
                new Thread(task,"Threads with return values:").start();;
            }
        }
        try{
            //Gets the return value of the thread
            System.out.println("Return Value of Subthread:"+task.get());
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Threads'life cycle

Each thread has to go through five states: new, ready, running, blocking, and death.

  • New state: The state when a thread object is created through the new keyword.

  • Ready state: that is, the state corresponding to the start() method of the thread object when starting the thread. At this time, the thread does not necessarily enter the running state immediately. The running of the thread is scheduled by the scheduling program of the operating system.

  • Running state: refers to the thread gains the execution power of the cpu, and the thread is executing the code that needs to be executed.

  • Blocking state: When the following happens, the thread enters blocking state.

    1. Threads call sleep() methods to actively relinquish the processor resources they occupy.
    2. Threads call a blocking IO method, blocking the thread before the method returns.
    3. Threads try to get a synchronization monitor, but the monitor is being held by other threads.
    4. The thread is waiting for a notify.
    5. The program calls the suspend() method of the thread to suspend it.
  • Dead state: Threads end in three ways, and the end of the thread is in a dead state.

    1. The run() method and call() method are executed, and the thread ends normally.
    2. Threads throw an Uncaptured Exception or Error.
    3. Call the stop() method of the thread directly to terminate the thread.

The state transition diagram of the thread is as follows:


Thread state transition

Note:

  1. Starting a thread uses the start() method of the thread object instead of calling the run() method directly. If the run() method is called directly, no thread is started to execute the method, as is the case with the normal object calling instance method. Starting a thread can only start a thread in the newly created state. Calling the thread object in the newly created state uses the start() method to start a thread. If the run() method or other method is called on a thread object in a new state, the thread object is no longer in a new state, and the start() method of calling the thread object in the future will not start a thread.
  2. For a thread in a dead state, the thread's start() method cannot be called again. The program can only call the start() method on threads in the newly created state. It is also wrong to call the start() method twice on threads in the newly created state, which will cause IllegalThreadStateException exception.

IV. Thread Control

1. join thread

The join() method is a method provided by Java's Thread class that allows one thread to wait for another thread to complete. When the join() method of other threads is called in the execution flow of a program, the calling thread will be blocked until the join thread joined by the join() method completes execution.

public class JoinThread extends Thread {
    //Provides a parametric constructor for setting the name of the thread
    public JoinThread(String name){
        super(name);
    }
    //Rewrite the run method to define the thread's executor
    public void run(){
        for(int i=0;i<100;i++){
            System.out.println(getName()+" "+i);
        }
    }
    public static void main(String[] args) throws InterruptedException {
        for(int i=0;i<100;i++){
            if(i==20){
                JoinThread jt=new JoinThread("cover join Threads");
                jt.start();
                //The main thread calls the join() method of the jt thread, and the main thread must wait for the jt thread to finish executing before it can go down.
                jt.join();
            }
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
}

2. Background threads

Daemon Thread runs in the background. Its task is to serve other threads, also known as daemon threads or wizard threads.
Characteristics of background threads: If all foreground threads die, background threads die automatically.
Calling the setDaemon(true) method of the Thread object can set a specified thread to a background thread.

public class DaemonThread extends Thread {
    public void run(){
        for(int i=0;i<1000;i++){
            System.out.println(getName()+" "+i);
        }
    }

    public static void main(String[] args) {
        DaemonThread dt=new DaemonThread();
        //Set this thread to a background thread
        dt.setDaemon(true);
        dt.start();
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
        //The program executes here, and the foreground thread main thread ends.
        //Background threads should also end with them
    }
}

3. Thread sleep: sleep

The sleep() method of the Thread class is used to suspend thread execution, and the thread calling sleep() will be blocked. The sleep() method of the Thread class is the static method of the Thread class.

public class SleepTest {
    public static void main(String[] args) throws Exception {
        for(int i=0;i<10;i++){
            System.out.println("Current time:"+new Date());
            //Call the sleep() method to suspend the current thread for 1s
            Thread.sleep(1000);
        }
    }
}

4. Thread concessions:yield

The yield() method is also a static method provided by the Thread class to suspend execution of threads. Unlike the sleep() method, the yeild() method does not block threads. When a thread calls the yield() method, the thread suspends execution and enters the ready state. Only threads with the same priority or higher priority than the current thread will be executed. Opportunities.

public class YieldTest extends Thread {
    public YieldTest(String name){
        super(name);
    }
    public void run(){
        for(int i=0;i<100;i++){
            System.out.println(getName()+" "+i);
            //When i=20, use yield() to make the current thread yield
            if(i==20){
                Thread.yield();
            }
        }
    }
    public static void main(String[] args) {
        //Start two concurrent threads
        YieldTest yt1=new YieldTest("senior");
        //Set yt1 thread to the highest priority
        //yt1.setPriority(MAX_PRIORITY);
        yt1.start();
        YieldTest yt2=new YieldTest("Lower");
        //Set yt2 threads to the lowest priority
        //yt2.setPriority(MIN_PRIORITY);
        yt2.start();
    }

Executing the above program will see that yt1 thread executes yield() method when i=20, because yt2 thread and yt1 thread are at the same priority level, so yt2 thread will get execution right, and then yt2 calls thread concession method yeild() when yt2 executes to i=20, for the same reason thread yt1 will get execution right.

5. Changing Thread Priority

Each thread in Java has a certain priority. Threads with higher priority have more opportunities to execute, while threads with lower priority have less opportunities to execute. For a thread created, Java defaults to the same priority as the parent thread that created it. If you want to change the priority of threads, you can use the setPriority (int new Priority) method provided by the Thread class to set the priority of threads, and the getPriority() method returns the priority of threads. The parameter range of priority in Java is an integer of 1-10.

Topics: Java Lambda less