Introduction to multithreading

Posted by cavedave on Mon, 29 Nov 2021 14:33:44 +0100

1, Concept

1. Concurrency and parallelism

  • Parallel: multiple tasks are executed simultaneously at the same time point. efficient
  • Concurrency: multiple tasks are executed at the same time. Low efficiency, macro simultaneous execution, micro time-sharing execution.

2. Process and thread

  • Process: the running application in memory, which is the basic unit for resource allocation and scheduling. Each process has its own independent running space and does not affect each other. A process is an execution process of a program, that is, a process from loading into memory to releasing from memory.
  • Thread: an independent running unit within a process. It is the smallest unit that the operating system can schedule operations. It is the actual operating unit in the process. A thread refers to a single sequential control flow in a process. Multiple threads can be concurrent in a process, and each thread executes different tasks in parallel.

 

  • Multithreading:   In a process, multiple threads can be started at the same time to allow multiple threads to perform certain tasks (functions) at the same time. The purpose of multithreading is to improve the running efficiency of programs.
  • Main thread: the running of any application has an independent running entry. The thread responsible for this entry is called the main thread of program operation. The main thread of a Java program is the main thread.

  3. Thread scheduling

1. The process of java program contains at least two threads, the main process is the main() method thread, and the other is the garbage collection mechanism thread. Whenever a java command is used to execute a class, a JVM will actually be started. Each JVM actually starts a thread in the operating system. java itself has a garbage collection mechanism, so at least two threads will be started when java runs.

2. Since the cost of creating a thread is much less than that of creating a process, we usually consider creating multiple threads rather than multiple processes when developing multitasking.

3. Because multiple threads in a process run concurrently, there is also a sequence from the micro point of view. Which thread is executed completely depends on the CPU scheduling, and programmers can't interfere. This leads to the randomness of multithreading.

There are two kinds of thread scheduling methods: time-sharing scheduling and preemptive scheduling (time slice rotation).

Time sharing scheduling: multiple tasks evenly allocate execution time.

Preemptive scheduling: threads seize the execution right of the CUP, and whoever grabs it will execute (randomness).

2, Thread class

  java.lang.Thread:   Represents a thread, which is the thread executing in the program. The Java virtual machine allows applications to execute multiple execution threads simultaneously.

1. Multithreading creation  

There are two ways to create a new execution thread.

  • Create a subclass and inherit the Thread class
  • Implement Runnable interface
  • The first way
  1. Declare a subclass of Thread, and declare a class to inherit Thread.
  2. Override the run() method in the subclass, that is, the thread task.
  3. Create a subclass object.
  4. Start multithreading.
  • Member method          
void run•() Thread task method. Code that needs to be executed by multiple threads is written in this method.
void start•() Start a new thread, java The virtual machine calls this thread run method.  

Declare Thread subclass  

// Declare Thread subclass
public class MyThread extends Thread {
    // Override the run() method to thread the task
    @Override
    public void run() {
        for( int i = 1; i <= 20; i++ ) {
            System.out.println("Wangcai..." + i );
        }
    }
}

Test code

public class Demo {
    public static void main(String[] args) {
        // Create subclass objects
        MyThread mt = new MyThread();
        // Start thread task
        // mt.run();  Calling the run () method directly cannot start a new thread
        mt.start(); // Open a new thread and call the run() method
​
        for( int i = 1; i <= 20; i++ ) {
            System.out.println("cockroach...." + i );
        }
    }
}

The difference between calling the run() method and the start() method

2. Thread name

  • After multithreaded objects are created, they all have default thread names. Naming rules:   Thread-x, starting from 0, increases one by one. We can name the thread through the method, and we can also get the name of the thread.
  • Constructor names the thread

Thread(String   name) assign a new   Thread   Object and name it.

  • The member method names the thread

void   setName•(String   name) change the name of this thread to equal the parameter   name  .   

  • Get thread name

String   getName • () returns the name of this thread.   

  • Get thread object

static   Thread   currentThread • () returns a reference to the thread object currently executing.

Thread code demonstration

public class ThreadName extends Thread {
    // Construction method
    public ThreadName() {
    }
    public ThreadName(String name) {
        super(name);
    }
​
    // Override the run() method to thread the task
    @Override
    public void run() {
        for( int i = 1; i <= 20; i++ ) {
            System.out.println(getName() + ".." + i );
        }
    }
}

  Test code demonstration

public class Demo {
    public static void main(String[] args) {
        // Create a thread object
        ThreadName tn1 = new ThreadName("Wangcai");
        // Gets the name of the thread
        System.out.println( tn1.getName() );
​
        // Create thread object 2
        ThreadName tn2 = new ThreadName();
        // Set thread name
        tn2.setName("Laifu..");
        // Gets the name of the thread
        System.out.println(tn2.getName());
​
        // Start thread
        tn1.start();
        tn2.start();
​
        // Main thread
        for( int i = 1; i <= 20; i++ ) {
            System.out.println(Thread.currentThread().getName() + i );
        }
    }
}

3. Thread priority

  • Each thread has priority, and the thread with higher priority takes precedence over the thread with lower priority. The priority range of threads is from   1-   10. The default priority is 5.

Priority generation method

  • int   getPriority • () returns the priority of this thread.   
  • void   setPriority•(int   Newpriority() to change the priority of this thread.   

  Thread code demonstration

public class ThreadPriority extends Thread {
    // Construction method
    public ThreadPriority() {
    }
    public ThreadPriority(String name) {
        super(name);
    }
    // Thread task
    @Override
    public void run() {
        for ( int i = 1; i <= 20; i++ ) {
            System.out.println( getName() + ".." + i );
        }
    }
}

  Test code demonstration

public class Demo {
    public static void main(String[] args) {
        // Create thread task
        ThreadPriority tp1 = new ThreadPriority("tp1.");
        ThreadPriority tp2 = new ThreadPriority("tp2..");
        ThreadPriority tp3 = new ThreadPriority("tp3...");
        // Set thread priority tp2 as the default priority
        tp1.setPriority(1);
        tp3.setPriority(10);
        // Get priority
        System.out.println( tp1.getPriority() );
        System.out.println( tp2.getPriority() );
        System.out.println( tp3.getPriority() );
        // Start thread
        tp1.start();
        tp2.start();
        tp3.start();
    }
}

4. Daemon thread

As described in the API, each thread may or may not be marked as a daemon, and is a daemon if and only if the creating thread is a daemon.

Threads are divided into user threads and daemon threads

  • User thread: that is, ordinary thread. All newly created threads belong to user thread.
  • Daemon thread: as the name suggests, daemon threads are used to guard and serve user threads. When there is no running user thread in the program, the daemon thread will end automatically. Garbage collection thread is a typical daemon thread.

Daemon method

  • void   setDaemon•(boolean   on) mark this thread as   daemon thread or user thread.   
  • boolean   isDaemon • () tests whether this thread is a daemon thread.

  Thread code demonstration

public class ThreadDaemon extends Thread {
    // Thread task
    @Override
    public void run() {
        for ( int i = 1; i <= 200; i++ ) {
            System.out.println( "Daemon thread.." + i );
        }
    }
}

  Test code demonstration

public class Demo {
    public static void main(String[] args) {
        // Create thread object
        ThreadDaemon td = new ThreadDaemon();
        // Set thread as daemon
        td.setDaemon(true);
        // Start thread
        td.start();
​
        /*
            Main thread loop
                At this time, the main thread is a user thread, and the main thread loops 20 times,
                The above daemon thread loops 200 times. Note that when the main thread executes
                After completion, the execution data of the daemon thread
          */
        for( int i = 1; i <=20;i++ ) {
            System.out.println("main.." + i );
        }
    }
}

5. Thread sleep

Dormancy method

  • static   void   sleep•(long   millis) stops (pauses) the currently executing thread for a specified number of milliseconds, depending on the accuracy and accuracy of the system timer and scheduler.   

Code demonstration

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        // Use the thread sleep method to make an alarm clock
        for( int i = 1;i <= 10; i++ ) {
            Thread.sleep(1000);
            System.out.println("The first" + i + "second");
        }
        System.out.println("Jingling bell...");
    }
}

3, Runnable interface

  • java.lang.Runnable:   Thread task interface, which should be implemented by any class, and its instances will be executed by threads. Class must define a parameterless method called run.

The second way of multithreading

1. Declare the implementation class of Runnable interface

2. Override the run() method in the implementation class

3. Create an implementation class object

4. Create a Thread object and pass the implementation class object to the constructor.

5. Start thread

Runnable implementation class

public class MyRunnable implements Runnable {
    // Thread task
    @Override
    public void run() {
        for( int i = 1; i <= 20; i++ ) {
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}

Test code demonstration

public class Demo {
    public static void main(String[] args) {
        // Create thread task object
        MyRunnable mr = new MyRunnable();
        // Create thread object
        Thread thread1 = new Thread(mr);
        Thread thread2 = new Thread(mr);
        // Start thread
        thread1.start();
        thread2.start();
    }
}

2. The difference between two thread creation methods

Breaking the single inheritance of Java

  • Java has single inheritance. A subclass can only inherit one parent class.
  • The first way to create a multithread is to inherit the Thread class. If this class has inherited other parent classes at this time, it cannot inherit Thread class. If you inherit Thread class instead, you will change the current inheritance system of this class.
  • The second creation method of multithreading does not require the class to be implemented by inheritance. While implementing the Runnable interface, it will not affect the original inheritance system of the class.

Implement class decoupling

  • The first way to create multithreading is that thread objects and thread tasks are directly coupled together.
  • The second creation method of multithreading realizes the decoupling of Thread object and Thread task. The Thread object is specially used to operate the Thread itself. The Thread task is extracted separately into the Runnable interface for independent operation. When a class implements the Runnable interface, it is equivalent to having a Thread task. After creating a Thread object and getting the Thread task, it can be executed, achieving the separation and combination of Thread task and Thread object.

4, Anonymous thread

The first way is to implement anonymous threads

public class Demo {
    public static void main(String[] args) {
        // Create Thread anonymous subclass
        new Thread(){
            // Thread task
            @Override
            public void run() {
                System.out.println("Thread start...");
            }
        }.start();
    }
}

The second way is to implement anonymous threads

public class Demo {
    public static void main(String[] args) {
        // Create an anonymous subclass of Thread to pass an anonymous Runnable object
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Thread start...");
            }
        }).start();
    }
}

Exercise: the principle of proximity, calling methods close to

public class Demo {
    public static void main(String[] args) {
        // Which code to run
        new Thread(new Runnable() {
            // Anonymous Runnable object
            @Override
            public void run() {
                System.out.println("Runnable Anonymous implementation class..Thread start...");
            }
        }){
            // Thread anonymous subclass
            @Override
            public void run() {
                System.out.println("Thread Anonymous subclass..Thread start...");
            }
        }.start();
    }
}

5. Thread safety

1. Thread safety analysis

  • Sometimes we need to use multithreading to operate shared data. Thread safety problems are particularly easy to occur in the process of operating shared data. Let's take a look at the causes of thread safety problems through a case.

case

  • Use multithreading to simulate train station ticketing, and each thread is equivalent to a ticketing window. The tickets sold by all windows are shared, that is, when one window sells a ticket, other windows cannot sell that ticket. There can be multiple windows, but each ticket is unique.

Thread task

public class Ticket implements Runnable {
    // Declare variables to simulate tickets
    private int num = 100;
    // The thread task is to sell tickets
    @Override
    public void run() {
        // The dead cycle simulation window is selling tickets all the time
        while ( true ) {
            // Determine whether there are still tickets
            if( num > 0 ) {
                // Get thread name
                String name = Thread.currentThread().getName();
                System.out.println( name + "sell ticket:" + num );
                // Thread hibernation, simulating ticket issuing time
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // The number of votes is iterated, minus the tickets sold
                num--;
            }
        }
    }
}

Test class

public class Demo {
    public static void main(String[] args) {
        // Create thread task object
        Ticket ticket = new Ticket();
        // Create thread object to simulate ticket window
        Thread t1 = new Thread(ticket,"Window one");
        Thread t2 = new Thread(ticket,"Window II");
        Thread t3 = new Thread(ticket,"Window three");
        Thread t4 = new Thread(ticket,"Window four");
        // Start thread
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

Operation results

  Cause analysis

  • Through the running results of the program, we can see that some tickets have been sold many times, while some tickets have not been sold but have been covered. The reason for the problem is that the multi thread operates the shared member variable num when executing the ticket selling task. However, in the process of operating num, when a thread operates on part of the code, the CPU switches to other threads to start executing thread tasks, which leads to inconsistent modification of the value of num variable.

Similar to the above problems, we call them thread safety problems. Causes of multithreading safety problems:

  1. There are multiple threads
  2. Multiple threads operate on shared data
  3. There is more than one statement to operate the shared data, and the shared data is modified
  • The essential reason is that when the CPU processes multiple threads, it switches between multiple codes operating shared data. Before the thread task of one thread ends directly, it switches to another thread.

Solution

  • According to the above problem analysis, we know that the security is caused by the random switching of CPU, but the CPU is controlled by the operating system. We can't directly intervene in the switching of CPU, so we can only start from the thread itself.

Solution: we can artificially control that when one thread is executing the code for operating shared data, other threads are not allowed to enter the code for operating shared data. Only when a thread finishes executing the code that operates the shared data, other threads can continue to execute the code that operates the shared statement. This ensures thread safety.

The above solution is called thread synchronization. There are three ways to realize thread synchronization:

  1. synchronized code block
  2. Synchronization method
  3. Lock lock mechanism

2. Synchronous code block

Syntax:

synchronized (Any unique lock object) {
    Code to manipulate shared data;
}
public class Ticket implements Runnable {
    // Declare variables to simulate tickets
    private int num = 100;
    // Declare lock object
    private Object lock = new Object();
    // The thread task is to sell tickets
    @Override
    public void run() {
        // The dead cycle simulation window is selling tickets all the time
        while ( true ) {
            // Synchronous code block
            synchronized ( lock ) {
                // Determine whether there are still tickets
                if (num > 0) {
                    // Get thread name
                    String name = Thread.currentThread().getName();
                    System.out.println(name + "sell ticket:" + num);
                    // Thread hibernation, simulating ticket issuing time
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // The number of votes is iterated, minus the tickets sold
                    num--;
                }
            }
        }
    }
}

3. Synchronization method

        When other methods are invoked in the run() method, the called method is also indirectly transformed into a thread task.

Synchronization method is a method modified with the synchronized keyword. When all the code in a method is code for operating shared data, we can directly declare the current method as a synchronization method.

grammar

public synchronized void methodName( Formal parameter ) {
    Code to manipulate shared data;        
}
public class Ticket implements Runnable {
    // Declare variables to simulate tickets
    private int num = 100;
    // The thread task is to sell tickets
    @Override
    public void run() {
        // The dead cycle simulation window is selling tickets all the time
        while ( true ) {
            ticket();
        }
    }
    // Declare a special ticketing method
    public synchronized void ticket() {
        // Determine whether there are still tickets
        if (num > 0) {
            // Get thread name
            String name = Thread.currentThread().getName();
            System.out.println(name + "sell ticket:" + num);
            // Thread hibernation, simulating ticket issuing time
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // The number of votes is iterated, minus the tickets sold
            num--;
        }
    }
}

matters needing attention

  1. Only when all the code in the method is the code that operates shared data, can synchronized be declared as a synchronized method, otherwise the execution efficiency will be reduced.
  2. The run() method cannot be decorated with synchronized, otherwise the thread task cannot be executed by multiple threads.

4. Inheritance and Realization of ticketing cases

Inheritance is not conducive to data sharing, so we need to consider how to realize data sharing.

public class Ticket extends Thread {
    // Declared variables simulate that tickets need to be static to ensure that tickets are shared
    private static int num = 100;
    // The declared lock object needs to be static to ensure that the lock is unique
    private static Object lock = new Object();
    // Constructor names the thread
    public Ticket(String name) {
        super(name);
    }
​
    // The thread task is to sell tickets
    @Override
    public void run() {
        // The dead cycle simulation window is selling tickets all the time
        while ( true ) {
            // Synchronous code block
            synchronized ( lock ) {
                // Determine whether there are still tickets
                if (num > 0) {
                    System.out.println(Thread.currentThread().getName() + "sell ticket:" + num);
                    // Simulated ticket issuing time
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // Iterative ticket
                    num--;
                }
            }
        }
    }
}

Test class

public class Demo {
    public static void main(String[] args) {
        // Create thread object simulation window
        Ticket ticket1 = new Ticket("Window one");
        Ticket ticket2 = new Ticket("Window II");
        Ticket ticket3 = new Ticket("Window three");
        Ticket ticket4 = new Ticket("Window four");
        // Start thread
        ticket1.start();
        ticket2.start();
        ticket3.start();
        ticket4.start();
    }
}

5. Synchronization details

When synchronization is added to the program, there are still thread safety problems. There are two reasons

  1. Lock not unique
  2. The synchronization code block is not added to the code that all operations share data

Benefits and disadvantages of synchronization:

  1. Using synchronization will affect the execution efficiency of the program. Each time the CPU runs to the synchronization code, it needs to judge whether there are threads in the synchronization code. If it can only wait for the execution of the threads in the synchronization code to end.
  2. The obvious advantage is that it can ensure the security of data.

Previously learned:

  • StringBuffer, which is thread safe. There is synchronization in the provided method. As long as there is synchronization, the efficiency will be reduced.
  • StringBuilder, which is thread unsafe.

JDK1.2 learned in collections appears, so some collections are thread unsafe, and those before JDK1.2 are thread safe.

Vector,Hashtable.

If thread safe operations on Collections are required during development, you need to use the methods in Collections to turn unsafe Collections into safe Collections.

6, Single case lazy thread safety problem  

Singleton design mode: only one object is allowed to be generated during the operation of the program.

The implementation process of the singleton design pattern consists of three steps:

  1. Private construction method.
  2. This class creates objects.
  3. Provides methods that expose static access to objects of this class.

The implementation of the code is divided into lazy and hungry.

Hungry Han style

public class Single { 
   // Private construction method
    private Single(){
        
    }
    // This class creates objects
    private static final Single s = new Single();
    // Returns the method of this class object
    public static Single getInstance() {
        return s;
    }
}

Lazy style

public class Single {
   // Private construction method
    private Single(){}
    // This class creates objects
    private static Single s = null;
    // Returns the method of this class object
    public static Single getInstance() {
        if ( s == null ) {
            s = new Single();
        }
        return s; // Return object
    }
}
  • In the above two methods, the hungry Chinese object creation process has only one statement, which will not cause thread safety problems. However, lazy objects create more than one statement. If there are multithreaded operations, thread safety problems will occur. Let's verify through the code that the thread task is to obtain the singleton object and check whether the object is unique.

Thread task

public class SingleThread implements Runnable {
    // The thread task is to get the singleton object
    @Override
    public void run() {
        // Get singleton object
        Single instance = Single.getInstance();
        // Print object
        System.out.println(instance);
    }
}

Test code

public class Demo {
    public static void main(String[] args) {
        // Create thread task object
        SingleThread st = new SingleThread();
        // Create thread object
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(st);
        Thread t3 = new Thread(st);
        Thread t4 = new Thread(st);
        // Start thread
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

Operation results

  •   When acquiring a singleton object through multiple threads, it is found that the object is not implemented. The only reason is that there are multiple statements to create the object, and we do not realize thread synchronization.

Thread synchronization: two judgments. The first judgment improves efficiency, and the second judgment is used to ensure thread safety

public class Single {
    // Declare lock object
    private static Object lock = new Object();
   // Private construction method
    private Single(){}
    // This class creates objects
    private static Single s = null;
    // Returns the method of this class object
    public static Single getInstance() {
        /*
            The judgment of going out can improve the running efficiency of the program. When a thread creates an object
            Other threads will not enter the synchronized code block.
          */
        if( s == null ) {
            // Synchronous code block
            synchronized (lock) {
                if (s == null) {
                    s = new Single();
                }
            }
        }
        // Return object
        return s;
    }
}

7, Deadlock problem of multithreading (avoid)

  • Deadlock: multiple threads operate to share data, but require threads to obtain locks in different order. But they must acquire all the locks according to their own order to perform this task. In the process of acquiring locks, different threads acquire locks in different ways, resulting in locks being occupied by other threads. Once this problem occurs, the deadlock problem occurs immediately.

case

  • There are two threads that need to perform the same task, but the A and B locks that need to be obtained respectively can be executed. The order in which the first thread obtains the lock is a first and then B. The order in which the second thread acquires the lock is B first and then a.

Thread task demo

public class DieThread implements Runnable{
    // Declare two lock objects
    private Object lock_A = new Object();
    private Object lock_B = new Object();
    // Declarative variables control the execution process
    boolean flag = false;
    @Override
    public void run() {
        if( flag ) {
            while ( true ) {
                // Get the synchronization code block of A lock
                synchronized (lock_A ) {
                    System.out.println("if..lock_A lock...");
                    // Synchronization code block for obtaining B lock
                    synchronized (lock_B) {
                        System.out.println("if..lock_B lock...");
                    }
                }
            }
        } else {
            while (true){
                // Get synchronization code of B lock
                synchronized (lock_B) {
                    System.out.println("else..lock_B lock...");
                    // Get the synchronization code block of A lock
                    synchronized (lock_A) {
                        System.out.println("else..lock_A lock...");
                    }
                }
            }
        }
    }
}

Test demonstration

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        // Create thread task object
        DieThread dt = new DieThread();
        // Create thread object
        Thread a = new Thread(dt);
        Thread b = new Thread(dt);
        // Start thread task
        a.start();
        // Thread sleep
        Thread.sleep(1);
        // Modify the value of flag so that the thread can enter the if statement
        dt.flag = true;
        // Start thread
        b.start();
    }
}

 

Topics: Java IntelliJ IDEA intellij-idea