Multithreaded applications

1. Process and thread

  a process is an instance of a program (such as QQ, game client, etc.). Thread is a control unit responsible for program execution in a process. A process can have multiple threads. The processes are basically independent of each other.

2. Parallelism and concurrency

  • Concurrency: do many things in turn at the same time
  • Parallel: do many things at the same time without interfering with each other

3. Synchronous and asynchronous

  • You need to wait for the result to return before you can continue running, which is synchronization
  • It is asynchronous to continue running without waiting for the result to return

4. Multithreading improves efficiency

  when the program is asynchronous, we can use multithreading to improve the efficiency of program execution

5. Create multithreading

(1) Create directly using Thread

  method 1 create multithreading

package com.basic;

public class Demo {

    public static void main(String[] args) {
        /*Thread t = new Thread() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()); // Output thread name
            }
        };*/

        // Simplifying with Lambda expression
        Thread t = new Thread(() -> System.out.println(Thread.currentThread().getName()));
        t.setName("t1"); // Specifies the thread name
        t.start(); // Turn on Multithreading
        System.out.println("Main thread running");
    }
}

(2) Create with Runnable and Thread

  method 2 create multithreading

package com.basic;

public class Demo {

    public static void main(String[] args) {
        /*Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        };*/

        // Simplifying with Lambda expression
        Runnable r = () -> System.out.println(Thread.currentThread().getName());
        Thread t = new Thread(r, "t2"); // Create a thread and specify the thread name
        t.start();
        System.out.println("Main thread running");
    }
}

  using Alt+Enter in idea can automatically change the gray code we write into Lambda form

(3) Relationship between thread and Runnable

  • Method 1 combines threads and tasks, and method 2 separates threads and tasks
  • Using Runnable makes it easier to work with advanced API s such as thread pools
  • Using Runnbale makes the Thread task class break away from the single inheritance system of Thread and more flexible

(4) FutureTask is created with Thread

   method 3 creates a multithread. Using this method to create a thread can obtain the execution result of the thread task

package com.basic;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Demo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // This generic is the data type that returns the result
        /*FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println(Thread.currentThread().getName());
                Thread.sleep(1000);
                return 100;
            }
        });*/

        // Simplifying with Lambda expression
        FutureTask<Integer> task = new FutureTask<>(() -> {
            System.out.println(Thread.currentThread().getName());
            Thread.sleep(1000);
            return 100;
        });

        Thread t = new Thread(task, "t3");
        t.start();

        System.out.println(task.get()); // Get the execution result of the thread task (it is blocked here until the thread result is obtained)
    }
}

6. Stack and stack frame

  • Each stack consists of multiple stack frames, corresponding to the memory occupied by each method call
  • Each thread can only have one active stack frame, corresponding to which method is currently executing
  • The stack memory of threads is independent of each other. Each thread has its own independent stack memory with independent stack frames. They run at the same time without interference with each other.

7. When to switch thread execution

  • The thread has run out of CPU time slices
  • Garbage collection, garbage collection will suspend all current working threads and let the garbage collection thread collect garbage
  • Threads with higher priority need to run
  • The thread calls sleep, yield, wait, join, park, synchronized, lock and other methods

  when this thread has not finished executing, switch to another thread and save the state information of the current thread. In Java, the program counter is used to record the execution position of the current thread. When the thread resumes running, it will continue to execute until the last execution position.
   status includes program counter and information of each stack frame in virtual machine stack, such as local variable, operand stack, return address, etc.
  frequent thread switching will affect performance, so the number of threads is not the more, the better. We should choose the appropriate number of threads.

8. Common methods

(1) start and run methods

  • start() method: start a new thread and run the run method in the new thread. The start method just makes the thread ready, and the code in it does not necessarily run immediately (the CPU time slice has not been allocated to it). The start method of each thread can only be called once. If it is called multiple times, an error will be reported
  • run() method: the method that will be called after the new thread starts you

(2) Method to get thread status

  thread object getState() method: get the state of the thread. The thread state in Java is represented by six enum s: NEW, RUNNABLE, BLOCKED, waiting and TIMED_WAITING,TERMINATED

(3) sleep method

   let the thread go to sleep, give up the use of CPU time slice during sleep time, let other threads run with CPU, and the thread enters the Timed Waiting state from Running state.
   the thread may not execute immediately after sleep (the thread cannot continue to execute until the task scheduler allocates a new time slice)

package com.basic;

public class Demo {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t1.start();
        Thread.sleep(1000); // The main thread waits for 1 second to check the status of t1 thread. Otherwise, the main thread runs first and cannot see the sleep status
        System.out.println("t1 Status of thread:" + t1.getState());
    }
}

  it is recommended to use the sleep method of TimeUnit instead of the sleep method of Thread, which has better readability.

package com.basic;

import java.util.concurrent.TimeUnit;

public class Demo {

    public static void main(String[] args) throws InterruptedException {
        TimeUnit.SECONDS.sleep(1); // Sleep for 1s, and the bottom layer also calls thread Sleep() method
    }
}

(4) yield method

  • The current thread calls thread Yield () method to give up the use right of the CPU and let the CPU execute other threads. Let the current thread enter the Runnable ready state from Running.
  • The specific implementation still depends on the task scheduler of the operating system
package com.basic;

public class Demo {

    public static void main(String[] args) {
        Runnable r1 = () -> {
            int i = 0;
            while (true) {
                Thread.yield(); // Relinquish the CPU usage of the thread
                System.out.println(Thread.currentThread().getName()+i++);
            }
        };

        Runnable r2 = () -> {
            int i = 0;
            while (true) {
                System.out.println(Thread.currentThread().getName()+i++);
            }
        };

        Thread t1 = new Thread(r1, "t1");
        Thread t2 = new Thread(r2, "t2");
        t1.start();
        t2.start();
    }
}

  from the above program, we can see that the number of thread 2 increases rapidly because thread 1 gives up the right to use the CPU during operation

(5) The difference between sleep and yield

   sleep is to make the current thread enter the Timed Waiting state from the running state, and yield is to make the current thread enter the Runnable state from the running state. When the thread is in the ready state, the task scheduler will still allocate time slices to the thread; However, when the thread is blocked, the task scheduler must wait until sleep time to allocate time slices.

(6) Set thread priority

  we can use thread objects Setpriority (number) method to set the priority of the thread. The larger the parameter, the higher the priority. 5 is the default priority, 1 is the minimum priority and 10 is the maximum priority. However, it is ultimately up to the task scheduler to determine the sequence of thread execution.

package com.basic;

public class Demo {

    public static void main(String[] args) {
        Runnable r1 = () -> {
            int i = 0;
            while (true) {
                System.out.println(Thread.currentThread().getName()+i++);
            }
        };

        Runnable r2 = () -> {
            int i = 0;
            while (true) {
                System.out.println(Thread.currentThread().getName()+i++);
            }
        };

        Thread t1 = new Thread(r1, "t1");
        Thread t2 = new Thread(r2, "t2");
        t1.setPriority(Thread.MIN_PRIORITY); // Set t1 thread to minimum priority
        t2.setPriority(Thread.MAX_PRIORITY); // Set t2 thread to maximum priority
        t1.start();
        t2.start();
    }
}

  from the above program, we can see that the number of thread 2 increases rapidly because thread 2 is the highest priority

(7) Use sleep method to prevent CPU from occupying 100%

package com.basic;

public class Demo {

    public static void main(String[] args) {
        while (true) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

   if it is a single core CPU, always while(true) will make the CPU run this thread all the time. If you add the sleep method and give up the time slice appropriately, you can prevent the CPU from occupying 100%

(8) join method (wait for a thread to finish running)

    use the join method to wait for the thread to finish running before executing further. Synchronization using the join method

  • You need to wait for the result to return before you can continue running, which is synchronization
  • It is asynchronous to continue running without waiting for the result to return
package com.basic;

public class Demo {

    private static int r = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r = 10;
        });
        t1.setName("t1");
        t1.start();
        t1.join(); // Wait until the t1 thread is finished, and then run down
        System.out.println(r);
    }
}

  time limited synchronization
   join(long n): wait for the thread to run. Wait for n milliseconds at most. If n milliseconds have not ended, continue to execute downward.

(9) interrupt method

  • Interrupt blocked threads

  calling sleep, wait and join methods will put the thread into a blocking state. Use the interrupt method to interrupt the blocking thread. At this time, the sleeping thread will not continue to execute and will directly throw the interruptedException exception.
   when a thread is interrupted, there is an interrupt flag. When a thread in blocking state is interrupted, it will indicate that it is interrupted in an abnormal way. Clear the interrupt flag and reset it to false

package com.basic;

public class Demo {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                System.out.println("If the thread sleep is interrupted, execute the following statement");
                e.printStackTrace();
            }
        }, "t1");
        t1.start();
        Thread.sleep(1000); // The main thread waits for 1 second to check the status of t1 thread. Otherwise, the main thread runs first and cannot see the sleep status
        t1.interrupt(); // Interrupt t1 thread sleep
        System.out.println("t1 Status of thread:" + t1.getState());
        System.out.println("t1 Break flag for Thread:" + t1.isInterrupted()); // false because the sleeping thread is interrupted
    }
}
  • Interrupt a running thread

   when the thread is running normally, we will interrupt it, and only set the interrupt flag to true, which will not affect the normal operation of the program. We can decide what to do next according to the break mark

package com.basic;

public class Demo {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    break; // If the thread is interrupted, exit the loop
                }
            }
        }, "t1");
        t1.start();
        Thread.sleep(1000);
        t1.interrupt();
    }
}
  • Thread object isInterrupted() method: judge whether the current thread is interrupted, and the interrupt mark will not be cleared
  • Thread.interrupted() method: judge whether the current thread is interrupted, and the interrupt flag will be cleared

(10) Design mode: two-stage termination mode

  how to "gracefully" terminate thread t2 in a thread t1? Here, the t2 thread is given a chance to take care of the aftermath.


  wrong idea:

  • Use the stop() method of the thread object to stop the thread. The stop method will really kill the thread. If the thread locks the shared resource at this time, it will never have a chance to release the lock after it is killed, and other threads will never be able to obtain the lock.

  two stage termination mode:

  take a termination monitoring thread as an example:

package com.basic;

public class Demo {

    public static void main(String[] args) throws InterruptedException {
        // Create a monitoring thread
        Thread monitor = new Thread(() -> {
            while(true) {
                if (Thread.currentThread().isInterrupted()) { // If interrupted
                    System.out.println("After care");
                    break;
                }

                // If it is not interrupted, every 1s (give up the time slice to prevent the CPU from idling all the time), and then perform the monitoring operation
                try {
                    Thread.sleep(1000); //Situation 1: interrupted during sleep
                    System.out.println("Perform monitoring operations"); // Case 2: interrupted during normal execution
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    // If the sleep is interrupted, the interrupt flag will be cleared and an exception will be reported, so we need to reset the interrupt flag to true
                    Thread.currentThread().interrupt();
                }

            }
        });
        monitor.start(); // Start monitoring thread

        Thread.sleep(3500);

        monitor.interrupt(); // Stop monitoring thread
    }
}

(11) interrupt interrupt park thread

  Park thread is the park() method in LockSupport class, which is used to stop the current thread

package com.basic;

import java.util.concurrent.locks.LockSupport;

public class Demo {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println("park.....");
            LockSupport.park(); // Let the thread stop in this line of code, and the following code will not be executed
            System.out.println("unpark...");
            System.out.println("Interrupt status:" + Thread.interrupted()); // Output the break flag and set the break flag to false

            LockSupport.park(); // When the break flag is true, park() will fail, so the above method is used to reset the break flag
            System.out.println("unpark......");
        }, "t1");
        t1.start();
        Thread.sleep(1000);
        t1.interrupt(); // Interrupt the park thread and let the thread continue to execute
    }
}

Added by ozzythaman on Mon, 13 Dec 2021 02:31:34 +0200