day26_ Introduction to multithreading, thread safety issues

Keywords: Java

Related concepts

What is multithreading?

Multithreading refers to the technology of concurrent execution of multiple threads from software or hardware. Computers with multithreading capability can execute multiple threads at the same time due to hardware support, so as to improve performance.

Concurrency and parallelism

  • Parallelism: multiple instructions are executed simultaneously on multiple CPU s at the same time.
  • Concurrency: at the same time, multiple instructions are executed alternately on a single CPU

Processes and threads

Process: refers to an application running in memory. Each process has an independent memory space. Process is also an execution process of the program and the basic unit of the system running the program.

  • Independence: a process is a basic unit that can run independently. It is also an independent unit for system resource allocation and scheduling
  • Dynamic: the essence of a process is an execution process of a program. A process is produced and dies dynamically
  • Concurrency: any process can execute concurrently with other processes

Thread: it is a single sequential control flow in a process and an execution path

  • Single thread: if a process has only one execution path, it is called a single threaded program
  • Multithreading: if a process has multiple execution paths, it is called a multithreaded program

Process is the smallest unit of operating system scheduling and resource allocation, and thread is the smallest unit of CPU scheduling. Memory is not shared between different processes. The cost of data exchange and communication between processes is very high. Different threads share the memory of the same process. Of course, different threads also have their own independent memory space. For the method area, the memory of the same object in the heap can be shared between threads, but the local variables of the stack are always independent.

Implementation of multithreading

Method 1: inherit Thread class

Java uses the java.lang.Thread class to represent threads. All Thread objects must be instances of the Thread class or its subclasses. The role of Thread is to complete certain tasks. In fact, it is to execute a program flow, that is, a piece of code executed in sequence. Java uses Thread executors to represent this program flow. The steps to create and start a multithread by inheriting the Thread class in Java are as follows:

  1. Define a subclass of the Thread class and override the run() method of the class. The method body of the run() method represents the tasks that the Thread needs to complete. Therefore, the run() method is called the Thread execution body.
  2. Create an instance of Thread subclass, that is, create a Thread object
  3. Call the start() method of the thread object to start the thread

be careful:

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

  • run(): encapsulates the code executed by the thread. It is called directly, which is equivalent to the call of ordinary methods
  • start(): start the thread; The run() method of this thread is then called by the JVM

  Code example

package ThreadDemo;
// 1: Define a class MyThread to inherit the Thread class
public class MyThread extends Thread {

    /**
     *2 : Rewrite the run method to complete the logic executed by the thread
     */
    @Override
    public void run() {
        for (int i = 0; i < 30; i++) {
            System.out.println("Executing!" + i);
        }

    }
}

  Define test class

package ThreadDemo;

public class Test {
    public static void main(String[] args) {
        //3: Create custom thread object
        MyThread mt = new MyThread();
        //4: Open new thread
        mt.start();
        //Execute the for loop in the main method, and the main thread code
        for (int i = 0; i < 30; i++) {
            System.out.println("main Thread!"+i);
        }
    }
}

Note: a custom thread object is a thread, that is, an execution path

Method 2: implement Runnable interface

Java has the limitation of single inheritance. When we cannot inherit the Thread class, what should we do? The Runnable interface is provided in the core class library. We can implement the Runnable interface, rewrite the run() method, and then start and execute our Thread body run() method through the object proxy of Thread class

The steps are as follows:

  1. Define the implementation class of the Runnable interface and override the run() method of the interface. The method body of the run() method is also the thread execution body of the thread.
  2. Create an instance of the Runnable implementation class and use this instance as the target of the Thread to create the Thread object, which is the real Thread object.
  3. Call the start() method of the thread object to start the thread.

The code is as follows:

  1. package demo;
    
    //1: Define a class MyRunnable to implement the Runnable interface
    public class MyRunnable implements Runnable {
        @Override
        public void run() {//2: Override the run() method in the MyRunnable class
            for(int i=0; i<100; i++) {
                System.out.println(i);
            }
        }
    }
     class MyRunnableDemo {
        public static void main(String[] args) {
            //3: Create an object of the MyRunnable class
            MyRunnable my = new MyRunnable();
    
            //4: Create an object of Thread class and take the MyRunnable object as the parameter of the construction method
            Thread t1 = new Thread(my);
    
            //5: Start thread
            t1.start();
    
        }
    }

Summary:

  • By implementing the Runnable interface, this class has the characteristics of multi-threaded class. The run() method is an execution target of a multithreaded program. All multithreaded code is in the run method. The Thread class is actually a class that implements the Runnable interface.
  • When you start the multithreading, you need to construct the object through the Thread class construction method Thread(Runnable target) and then call the start() method of the Thread object to run the multithreaded code.
  • In fact, all multithreaded code is run by running the start() method of Thread. Therefore, whether inheriting the Thread class or implementing the Runnable interface to realize multithreading, the Thread is finally controlled through the API of the Thread object. Being familiar with the API of the Thread class is the basis of multithreading programming.
  • The Runnable object is only used as the target of the Thread object, and the run() method contained in the Runnable implementation class is only used as the Thread executor. The actual Thread object is still a Thread instance, but the Thread thread is responsible for executing the run() method of its target.

Implementation of multithreading mode 3: implementation of Callable interface

Implementation steps

  1. Define a class MyCallable to implement the Callable interface
  2. Override the call() method in the MyCallable class
  3. Create an object of the MyCallable class
  4. Create the FutureTask object of the implementation class of Future, and take the MyCallable object as the parameter of the construction method
  5. Create an object of Thread class and take FutureTask object as the parameter of construction method
  6. Start thread
  7. Then call the get method to get the result after the thread ends.

Code example

package demo01;

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

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println("physical exercise:" + i);
        }
        //The return value represents the result after the thread runs
        return "Eight abdominal muscles";
    }
}

class Test{
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //After the thread is started, you need to execute the call method inside
        MyCallable myCallable = new MyCallable();
        //task can obtain the result after the Thread is executed. It can also be passed to the Thread object as a parameter
        FutureTask<String> task = new FutureTask<>(myCallable);
        //Create thread object
        Thread thread = new Thread(task);
        //Open thread
        thread.start();
        // Gets the object after the thread is executed
        String s = task.get();
        System.out.println(s);
    }
}

Comparison of three methods:

Thread class

Construction method:

  • public Thread(): allocate a new thread object.
  • public Thread(String name): assign a new thread object with a specified name.
  • public Thread(Runnable target): allocate a new thread object with the specified target.
  • public Thread(Runnable target,String name): allocate a new thread object with the specified target and specify the name.

common method

  • public void run(): the task to be executed by this thread defines the code here.
  • public String getName(): get the name of the current Thread. A Thread has a default name. The format is Thread number
  • public static Thread currentThread(): returns a reference to the currently executing thread object.
  • public final boolean isAlive(): test whether the thread is active. Active if the thread has been started and not terminated.
  • public final int getPriority(): returns the thread priority
  • public final void setPriority(int newPriority): change the priority of threads. Each Thread has a certain priority. Threads with high priority will get more execution opportunities. The default priority of each Thread is the same as the parent Thread that created it. The Thread class provides setPriority(int newPriority) and getPriority() method classes to set and obtain the priority of threads. The setPriority method requires an integer and the range is between [1,10]. By default, the main Thread has normal priority 5.
  • public void start(): causes this thread to start executing; The Java virtual machine calls the run method of this thread.
  • Public static void sleep (long miles): pauses (temporarily stops) the currently executing thread for the specified number of milliseconds.
  • public static void yield(): yield just pauses the current thread and reschedules the system's thread scheduler. It is hoped that other threads with the same or higher priority as the current thread can get execution opportunities. However, this cannot be guaranteed. It is entirely possible that when a thread calls the yield method to pause, The thread scheduler schedules it for re execution.
  • void join(): wait for the thread to terminate.
  • void join(long millis): the maximum time to wait for the thread to terminate is millis. If the millis time expires, it will no longer wait.
  • void join(long millis, int nanos): the maximum time to wait for the thread to terminate is millis + nanos.
  • public final void stop(): force the thread to stop execution. This method is inherently unsafe. It has been marked @ Deprecated and is not recommended to be used again. Then we need to stop the thread in other ways. One way is to use the change of variable value to control whether the thread ends.

Daemon thread

There is a thread that runs in the background. Its task is to provide services for other threads. This thread is called "guard thread". The garbage collection thread of the JVM is a typical daemon thread. A feature of daemon threads is that if all non daemon threads die, the daemon thread will die automatically.

  • Call the setDaemon(true) method to set the specified thread as a daemon thread. It must be set before the thread starts, otherwise an IllegalThreadStateException will be reported.
  • Call isDaemon() to determine whether the thread is a daemon thread.

Thread safety

When we use multiple threads to access the same resource (which can be the same variable, the same file, the same record, etc.), if multiple threads only have read operations, thread safety problems will not occur, but if multiple threads have read and write operations on resources, thread safety problems are easy to occur.

Same resource problem

  • Local variables cannot be shared
  • Instance variables of different objects are not shared
  • Static variables are shared
  • Instance variables of the same object are shared

View the following code

package demo06;

public class SellTicket implements Runnable {
    private int ticket = 100;
    //Rewrite the run() method in SellTicket class to realize ticket selling. The code steps are as follows
    @Override
    public void run() {
        while (true) {
            if(ticket <= 0){
                //out of stock
                break;
            }else{
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticket--;
                System.out.println(Thread.currentThread().getName() + "Selling tickets,Remaining" + ticket + "Ticket");
            }
        }
    }
}

Define test class

package demo06;

public class Test {
    public static void main(String[] args) {
        //Create an object of SellTicket class
        SellTicket st = new SellTicket();

        //Create three Thread class objects, take the SellTicket object as the parameter of the construction method, and give the corresponding window name
        Thread t1 = new Thread(st,"Window 1");
        Thread t2 = new Thread(st,"Window 2");
        Thread t3 = new Thread(st,"Window 3");

        //Start thread
        t1.start();
        t2.start();
        t3.start();
    }
}

Results after execution

  Through the implementation results, we found that

  • The same ticket appeared many times
  • There were negative votes

Causes of problems

  • Due to the randomness of thread execution, the execution right of cpu may be lost during ticket selling, resulting in problems

Conditions for safety problems

  • Is a multithreaded environment
  • Shared data
  • Multiple statements operate on shared data

How to solve the problem of multithreading security? Basic idea: let the program have no security environment. Java provides a synchronized mechanism to solve the problem.

In order to ensure that each thread can normally perform atomic operations, Java introduces thread synchronization mechanism. Note: at most one thread is allowed to have a synchronization lock at any time. Whoever gets the lock will enter the code block, and other threads can only wait outside (BLOCKED).  

Advantages and disadvantages of synchronization

  • Benefits: it solves the data security problem of multithreading
  • Disadvantages: when there are many threads, each thread will judge the lock on synchronization, which is very resource-consuming and will virtually reduce the running efficiency of the program

Synchronous code block

The synchronized keyword can be used in front of a block to indicate that mutually exclusive access is only implemented to the resources of the block.

format

Code example

public class SellTicket implements Runnable {
    private int tickets = 100;
    private Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (obj) { // Lock the code that may have security problems. Multiple threads must use the same lock
                //When t1 comes in, it will lock this code
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                        //t1 rest 100ms
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //Window 1 is selling ticket 100
                    System.out.println(Thread.currentThread().getName() + "The second is being sold" + tickets + "Ticket");
                    tickets--; //tickets = 99;
                }
            }
            //When t1 comes out, the lock of this code is released
        }
    }
}

public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket st = new SellTicket();

        Thread t1 = new Thread(st, "Window 1");
        Thread t2 = new Thread(st, "Window 2");
        Thread t3 = new Thread(st, "Window 3");

        t1.start();
        t2.start();
        t3.start();
    }
}

What is the synchronization lock object?

  • The lock object can be of any type.
  • Multiple thread objects should use the same lock.

Synchronization method

The synchronized keyword directly modifies a method, indicating that only one thread can enter the method at the same time, and other threads are waiting outside.

Synchronization method: add the synchronized keyword to the method. The format is as follows:

Synchronous static method: add the synchronized keyword to the static method

  Code example

package demo02;

public class SaleTicketSafeDemo1 {
    public static void main(String[] args) {
        // 2. Create resource object
        Ticket ticket = new Ticket();

        // 3. Start multiple threads to operate on the object of the resource class
        Thread t1 = new Thread("Window one") {
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(10);// Add this to make the problem more obvious
                        ticket.sale();
                    } catch (Exception e) {
                        e.printStackTrace();
                        break;
                    }
                }
            }
        };
        Thread t2 = new Thread("Window II") {
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(10);// Add this to make the problem more obvious
                        ticket.sale();
                    } catch (Exception e) {
                        e.printStackTrace();
                        break;
                    }
                }
            }
        };
        Thread t3 = new Thread(new Runnable() {
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(10);// Add this to make the problem more obvious
                        ticket.sale();
                    } catch (Exception e) {
                        e.printStackTrace();
                        break;
                    }
                }
            }
        }, "Window three");

        t1.start();
        t2.start();
        t3.start();
    }
}

// 1. Write resource class
class Ticket {
    private int total = 10;

    //The lock object implied by non static methods is this
    public synchronized void sale() {
        if (total > 0) {
            System.out.println(Thread.currentThread().getName() + "Sell one ticket and the rest:" + --total);
        } else {
           return;
        }
    }


}

Lock object problem of synchronization method

  • Static method: the Class object of the current Class
  • Non static method: this

Lock lock

  • Although we can understand the Lock object problem of synchronization code blocks and synchronization methods, we do not directly see where the Lock is added and where the Lock is released. In order to more clearly express how to add and release the Lock, JDK5 provides a new Lock object Lock
  • Lock is an interface that cannot be instantiated directly. Its implementation class ReentrantLock is used to instantiate it here

Common methods:

  • void lock():   Acquire lock   
  • void unlock(): release the lock
package demo02;

import java.util.concurrent.locks.ReentrantLock;

public class Ticket implements Runnable {
    //Number of tickets
    private int ticket = 100;
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {

            try {
                lock.lock();
                if (ticket <= 0) {
                    //out of stock
                    break;
                } else {
                    Thread.sleep(100);
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "Selling tickets,Remaining" + ticket + "Ticket");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        }
    }
}

 class Demo {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();

        Thread t1 = new Thread(ticket);
        Thread t2 = new Thread(ticket);
        Thread t3 = new Thread(ticket);

        t1.setName("Window one");
        t2.setName("Window II");
        t3.setName("Window three");

        t1.start();
        t2.start();
        t3.start();
    }
}

deadlock

Thread deadlock refers to that two or more threads hold the resources required by each other, resulting in these threads being in a waiting state and unable to execute

Under what circumstances will deadlock occur

  • Limited resources
  • Synchronous nesting

Code demonstration

public class Demo {
    public static void main(String[] args) {
        Object objA = new Object();
        Object objB = new Object();

        new Thread(()->{
            while(true){
                synchronized (objA){
                    //Thread one
                    synchronized (objB){
                        System.out.println("Well off students are walking");
                    }
                }
            }
        }).start();

        new Thread(()->{
            while(true){
                synchronized (objB){
                    //Thread two
                    synchronized (objA){
                        System.out.println("Xiao Wei is walking");
                    }
                }
            }
        }).start();
    }
}

Posted by tkj on Sun, 19 Sep 2021 07:53:19 -0700