Java notes - multithreading

Keywords: Java Multithreading

Process and thread

  1. Process:
    The running program is an independent unit for the system to allocate and call resources.
    Each process has its own memory space and system resources
  2. Thread:
    It is a single sequential control flow in a process, or a separate execution path
    If a process has only one execution path, it is called a single threaded program. If a process has multiple execution paths, it is called a multithreaded program.
    Threads are contained in a process.

Parallelism and concurrency

Parallelism should be that two or more events can occur at the same time. The metaphor is that when the phone comes, put down your chopsticks and make a phone call, and then eat after the phone call. Because only one person has only one mouth and can't call and eat at the same time, it takes two mouths to call and eat at the same time. It is emphasized that it is implemented at the same time without interference with each other.

Concurrency occurs logically at the same time. Two or more events occur within the same time interval. The metaphor is to eat while talking on the phone, eat a meal at a very small interval, and say a word. The two occur alternately. It seems to happen at the same time on the macro level, but it is not. The emphasis is to use small time periods to complete alternately.

How Java programs work:

The java command will start the JVM, which is equivalent to starting an application, that is, starting a process.
The process will automatically start a "main thread", and then the main thread will call the main method of a class. So the main method runs in the main thread. In other words, like the previous process of learning java, the programs written are actually single threaded.

The JVM itself is multithreaded when it is started, because the JVM has at least two threads: the main thread and the garbage collection thread.

Implementation of multithreading

Implementation method 1: inherit the Thread class and override the run() method
package test.MyThread;

public class MyThreadDemo1 extends Thread {
    @Override
    public void run(){
        for(int i=0;i<10;i++){
            System.out.println(i);
        }
    }
}

package test.MyThread;

public class Demo1 {
    public static void main(String[] args) {
        //Create two threads m1 and m2
        MyThreadDemo1 m1 = new MyThreadDemo1();
        MyThreadDemo1 m2 = new MyThreadDemo1();

        //To call the start() method, first start a thread separately, and then the JVM calls the run() method of the thread
        m1.start();
        m2.start();
    }

}

The result is printed from 0 to 10 and twice in a row

Note here:

  1. You can't call the run method directly with a thread object. Calling the run method directly is equivalent to calling an ordinary method, which has nothing to do with multithreading. To see the effect of multithreading, you must start it with start()
  2. The difference between calling run() and calling start()
    The call of run() only encapsulates the code executed by the thread, but the direct call is an ordinary method call
    To call the start() method, first start a thread separately, and then the JVM calls the run() method of the thread
  3. When a thread starts, it calls the start() method, but it actually calls the body defined by the run() method
  4. The same thread cannot be started twice by start(). If this is done, an error java.lang.IllegalThreadStateException will be reported
Gets and sets the thread name
  1. Name the thread through the constructor:
    Thread(String name) assigns a new thread object.
  2. Name the thread through the method:
    void setName(String name) changes the name of this thread to be equal to the parameter name
package test.MyThread;

public class MyThreadDemo2 extends Thread{
//Name the thread through the constructor
    public MyThreadDemo2(){
        super();
    }

    public MyThreadDemo2(String name){
        super(name);
    }

    @Override
    public void run(){
        for(int i=0;i<10;i++){
            System.out.println(getName()+"---"+i);
        }
    }
}

package test.MyThread;

public class Demo2 {
    public static void main(String[] args) {
        //Method 1: name the thread through the construction method
        MyThreadDemo2 m1 = new MyThreadDemo2("Zhang");
        MyThreadDemo2 m2 = new MyThreadDemo2("Chen");

        m1.start();
        m2.start();

        //Method 2: name the thread through the method
        MyThreadDemo2 m3 = new MyThreadDemo2();
        MyThreadDemo2 m4 = new MyThreadDemo2();
        MyThreadDemo2 m5 = new MyThreadDemo2();

        m3.setName("Du");
        m4.setName("week");

        m3.start();
        m4.start();
        m5.start();

        //public static Thread currentThread() returns a reference to the thread object currently executing
        //Get the current thread object through currentThread(), and then get the thread name through getName()
        //Take the main thread of the main method of this program as an example
        System.out.println(Thread.currentThread().getName());

    }
}

The result is too long. Only part of the picture is intercepted

The process of thread preempting cpu is analyzed from the results

It can be seen from this picture that threads occupy the cpu in a way of snatching, and only threads occupying the cpu can run. Therefore, when thread m1 successfully occupies cpu, run the method body of the run() method and print the output statement "zhang-0". All threads run alternately, and each thread can only occupy a short period of cpu time. This seems to run together, but it is not. Therefore, after thread m1 runs, thread m3 preempts the cpu and prints the statement "du-0".

In addition, there is a string "main", which is System.out.println(Thread.currentThread().getName()); Output result of statement

At the bottom of the picture is Thread-2. This is the name of the thread object m5. I deliberately didn't give m5 a name. You can see here that if you don't name yourself, the system will automatically give a name.

Implementation mode 2: implement Runnable interface

1. Create a custom class to implement the Runnable interface
2. Override the run() method
3. Create an object of a custom class
4. Create an object of Thread class, and pass the custom class object created in step 3 as a parameter to the construction method

package test.MyThread;

public class MyRunnable implements Runnable {
    @Override
    public void run(){
        for(int i=0;i<100;i++){
           
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
    }
}

Explanation of the statement Thread.currentThread().getName():
Since there is no getName() method in the Runnable interface implemented, the getName() method in the Thread class cannot be used here
At this time, if we get the name of the current thread, we need to call indirectly, that is, get the thread object currently executing the run() method through currentThread(), and then get the thread name through getName()
The Thread object obtained by currentThread() is of type Thread, so you can use the getName() method

package test.MyThread;

public class Demo3 {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();

        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);

        t1.setName("Zhang");
        t2.setName("Chen");
        t1.start();
        t2.start();
    }
}

thread priority

Gets the priority of the thread
public final int getPriority() returns the priority of this thread.
Set thread priority
public final void setPriority(int newPriority) changes the priority of this thread. The parameter newPriority ranges from 1 to 10

be careful:

  1. The default priority for threads is 5
  2. The thread priority range is 1-10
  3. High thread priority only means that the probability of obtaining CPU time slice will be higher, but it does not mean that the thread with high priority can grab the CPU
package test.MyThread;

public class MyPriority extends Thread {
    @Override
    public void run(){
        for(int i=0;i<10;i++){
            System.out.println(getName()+"---"+i);
        }
    }
}

package test.MyThread;

public class Demo4 {
    public static void main(String[] args) {
        MyPriority m1 = new MyPriority();
        MyPriority m2 = new MyPriority();

        //Gets the priority of m1 and m2
        System.out.println(m1.getPriority());
        System.out.println(m2.getPriority());

        //Set the priority of m1 and m2
        m1.setPriority(1);
        m2.setPriority(10);

        //Start thread
        m1.start();
        m2.start();
    }
}


It can be seen that the probability of thread m2 grabbing the CPU is much higher

Join thread

Join thread:
public final void join(): other threads wait for this thread to die
matters needing attention:
Before the thread is set to join the thread, first change the thread to the ready state, that is, call the start() method

package test.MyThread;

public class JoinThread extends Thread{
    @Override
    public void run(){
        for(int i=0;i<10;i++){
            System.out.println(getName()+"---"+i);
        }
    }
}

package test.MyThread;

public class Demo5 {
    public static void main(String[] args) {
        JoinThread j1 = new JoinThread();
        JoinThread j2 = new JoinThread();
        JoinThread j3 = new JoinThread();

        j1.setName("Zhang");
        j2.setName("week");
        j3.setName("Chen");

        j1.start();

        try {
            j1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        j2.start();
        j3.start();
    }
}


It can be seen that j2 and j3 do not normally seize the cpu to run the program until all threads j1 run

Thread sleep

package test.MyThread;

public class SleepThread extends Thread{
    @Override
    public void run(){
        for(int i=0;i<10;i++){
            System.out.println(getName()+"---"+i);
            try {
                //sleep(long millis)millis is the number of milliseconds. One second is equal to 1000 milliseconds
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

package test.MyThread;

public class Demo6 {
    public static void main(String[] args) {
        SleepThread s1 = new SleepThread();
        SleepThread s2 = new SleepThread();
        SleepThread s3 = new SleepThread();

        s1.setName("Zhang");
        s2.setName("week");
        s3.setName("Chen");

        s1.start();
        s2.start();
        s3.start();
    }
}


When actually printing results on the console, follow
Zhang-0
Week - 0
Chen-0
In this way, the order of three groups is printed every second until the printing is completed, but the internal order can be changed, such as
Week 1
Zhang-1
Chen-1
Because the three processes sleep for one second every time after seizing the CPU to print, the sleep time of the first process is enough for the rest of the processes to complete an output, and process m1 will sleep after printing without seizing the CPU. Then m2 prints sleep, and finally m3 prints sleep. It will not happen that m1 starts to seize the CPU immediately after printing. So print in groups of three. However, after the three processes sleep, they will seize the CPU again, so the order in the group is different.

Interrupt thread

public final void stop(): stop the running thread. The remaining code of the run method will not be executed, and this method is obsolete and deprecated
public void interrupt(): interrupt the running thread. The interrupted thread will complete the execution of the run method and throw an exception

Normally executed code
import java.util.Date;

public class MyStopThread extends Thread {
    @Override
    public void run() {
        System.out.println("Start execution time:" + new Date());

        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Execution end time:" + new Date());
        System.out.println("test");
    }
}

package test.MyThread;

public class ThreadStopDemo {
    public static void main(String[] args) {
        MyStopThread t1 = new MyStopThread();

        t1.start();

    }
}


There is an interval of ten seconds between the start time of execution and the end time of execution

Code to interrupt execution
package test.MyThread;

public class ThreadStopDemo {
    public static void main(String[] args) {
        MyStopThread t1 = new MyStopThread();

        t1.start();

        try {
            Thread.sleep(3000);
            t1.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


Thread.sleep(3000) here; It refers to the main thread, that is, the main thread calling the main method, and pauses for three seconds. Then interrupt the operation of thread t1
Thread t1 was supposed to sleep for ten seconds and then end. Here, because interrupt interrupted t1's sleep, there is only three seconds between the end execution time and the start execution time

Comity thread

Comity thread:
public static void yield()
Pauses the currently executing thread object and executes other threads
Its function is to make multiple threads more harmonious, and it does not necessarily guarantee that multiple threads can be executed by one person at a time

package test.MyThread;

public class YieldThread extends Thread{
    @Override
    public void run(){
        for(int i=0;i<10;i++){
            System.out.println(getName()+"---"+i);
        }
    }
}

package test.MyThread;

public class Demo8 {
    public static void main(String[] args) {
        YieldThread y1 = new YieldThread();
        YieldThread y2 = new YieldThread();

        y1.start();
        y2.start();
    }
}


Similar to such a person's sentence, the process will give way to the resources after occupying the CPU and printing. Of course, it is also possible to grab the resources after giving way

Background thread (daemon thread)

There are two types of threads in Java: user thread and daemon thread
User thread: before we learn multithreading, all program code runs one user thread after another.
Daemon thread: the so-called daemon thread refers to the thread that provides a general service in the background when the program is running. for instance
Garbage collection thread is a daemon thread, which is not an indispensable part of the program. Therefore, when the non daemon thread ends, the program will terminate and kill all the daemon threads at the same time. Conversely, as long as there are non daemon threads in the program, the program will not terminate.

Setting method of daemon thread:
public final void setDaemon(boolean on)
This method marks the thread object as a daemon thread or a non daemon thread.
When the running program has only one thread and is a daemon thread, the Java virtual machine exits

Note: the step of setting the thread as a daemon thread must be set before startup.

package test.MyThread;

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

package test.MyThread;

public class Demo9 {
    public static void main(String[] args) {
        DaemonThread d1 = new DaemonThread();
        DaemonThread d2 = new DaemonThread();
        DaemonThread d3 = new DaemonThread();

        d2.setDaemon(true);
        d3.setDaemon(true);

        d1.start();
        d2.start();
        d3.start();
    }
}


When the non daemon thread d1 ends, the daemon threads d2 and d3 will end immediately, even if the loop has not ended

Posted by hagman on Fri, 22 Oct 2021 07:43:32 -0700