Java day17: multithreading (the last knowledge lesson of Java SE)

Posted by lemonpiesaregood on Sat, 25 Dec 2021 15:18:07 +0100

1, Program, process and multitasking

A program is a collection of codes that describe and operate data. It is a script executed by an application program.

Process is an execution process of a program and the basic unit for the system to run the program.

The program is static and the process is dynamic. A system running a program is a process from creation, running to extinction.

multi task can run multiple programs at the same time in a system, that is, there are multiple independent tasks, and each task corresponds to a process.

What is concurrency and parallelism

To learn multithreading, you must first understand what concurrency and parallelism are

Parallel: two or more events occur at the same time (at the same time).

Concurrency: two or more events occur in the same time period.

What are processes and threads

Process:

A process is an instance of a running program.

A process is a container for threads, that is, multiple threads can be opened in a process.

For example, opening a browser, opening a word and other operations will create a process.

Thread:

Thread is an independent execution unit in a process;

A process can run multiple threads simultaneously;

For example, a process can be understood as a hospital. Threads are business activities such as registration, medical treatment, payment and taking medicine

Multithreading:

Multiple threads execute concurrently.

Gets the name of the current thread thread currentThread(). getName()
1.start():1. Start the current thread 2 Call the run method in the thread
 2.run(): you usually need to override this method in the Thread class and declare the operation to be performed by the created Thread in this method
 3.currentThread(): a static method that returns the thread executing the current code
 4.getName(): get the name of the current thread
 5.setName(): sets the name of the current thread
 6.yield(): actively release the execution authority of the current thread
 7.join(): insert and execute another thread into the thread. The thread is blocked. The thread will not continue to execute until the thread inserted and executed is completely executed
 8.stop(): obsolete method. When this method is executed, the current thread is forced to end.
9.sleep (long millitime): a thread sleeps for a period of time
 10.isAlive(): judge whether the current thread is alive

public class MyThead implements Runnable {
	private String name;

	public MyThead(String name) {
		this.name = name;
	}

	@Override
	public void run() {
		for(int i=0; i<=100; i++) {
			System.out.println(this.name + "=====" + i);
			if(i == 30) {
//				try {
//					Thread.sleep(10*1000); //1s=1000ms
//				} catch (InterruptedException e) {
//					e.printStackTrace();
//				} 
				Thread.yield();
			}
		}
	}

}

public class Test2 {

	public static void main(String[] args) {
		//Instantiate MyThread object
		MyThead mt1 = new MyThead("My name is thread 1");
		MyThead mt2 = new MyThead("My name is thread 2");
		
		//Create thread object
		Thread th1 = new Thread(mt1);
		Thread th2 = new Thread(mt2);
		
		//Start thread
		th1.start();
		th2.start();
	}

}

public class FeiYuqing extends Thread {

	@Override
	public void run() {
		int race = 100;
		while(race > 0) {
			int x = (int) (Math.random()*10); //Randomly generate positive numbers between 0 and 10
			if(race - x >= 0) {
				race -= x;
				try {
					sleep(1*1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("¥Fei Yuqing and"+race+"Mi escaped successfully!!");
		}
		System.out.println("*******************Fei Yuqing escaped successfully!!!!");
	}

}


public class DaXingxing extends Thread {

	@Override
	public void run() {
		int race = 100;
		while(race > 0){
			int x = (int) (Math.random()*10);
			if(race - x  >= 0) {
				race -= x;
				try {
					sleep(1*1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("@Gorillas and"+race+"Mi caught Fei Yuqing. Come on!");
		}
		System.out.println("************The gorilla has caught Fei Yuqing. I wish you happiness!");
	}

}


public class Test2 {

	public static void main(String[] args) {
		FeiYuqing fyq = new FeiYuqing();
		DaXingxing dxx = new DaXingxing();
		
		fyq.start();
		dxx.start();
	}

}

2, Multithreading Foundation

1. What is concurrency and parallelism

Parallel: multiple CPU s execute multiple tasks at the same time, for example, multiple people do different things at the same time

Concurrency: one CPU (using time slice) executes multiple tasks at the same time, such as seckill platform, and multiple people do the same thing

When you are halfway through your meal and the phone comes, you stop and answer the phone. After that, you continue to eat, which shows that you support concurrency.

When you call halfway through your meal, you eat while you call, which shows that you support parallelism.

Concurrency means that two queues use one coffee machine alternately, and parallelism means that two queues use two coffee machines at the same time,

Concurrency and parallelism can be many threads. It depends on whether these threads can be executed by (multiple) CPUs at the same time. If so, it means that they are parallel, and concurrency means that multiple threads are switched and executed by (one) cpu in turn.

2. What are processes and threads

Process: it is an execution process of a program, or a running program. It is a dynamic process with its own process of generation, existence and extinction.

Thread: a process can be further refined into a thread, which is an execution path within a program.

Process definition: a process can describe the execution process of a program.

Process: a dynamic execution process of a program with certain independent functions on a data set. Only when a program is loaded into memory by OS and executed by cpu, this process is dynamic, which is called process.

Thread is similar to process, but thread is a smaller execution and running unit than process.

A process can produce multiple threads during its execution.

Different from processes, multiple threads of the same kind share the same memory space and a group of system resources, so the burden of threads generated by the system is much smaller than that of processes. Threads are also called lightweight processes.

A program is a file containing instructions and data, which is stored on disk or other data storage devices, that is, the program is static code.

A process is a program in execution. A process is an execution process of a program and the basic unit of the system running program. Therefore, the process is dynamic.

The biggest difference between threads and processes is that each process is basically independent, while each thread is not necessarily independent. Because threads in the same process are likely to affect each other. On the other hand, a process belongs to the category of the operating system. It is mainly that more than one program can be executed at the same time in the same period of time, while a thread executes more than one program segment almost at the same time in the same program.

3. Thread creation

Method 1: inherit Thread class

Define subclasses that inherit the Thread class.

Override the run method in the Thread class in the subclass.

Create a Thread subclass object, that is, create a Thread object.

Call the thread object start method: start the thread and call the run method

//Step 1: create a custom thread class
package com.multithread.thread;

import java.util.Date;

public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i<10; i++){
            System.out.println("mythread Thread executing:"+new Date().getTime());
        }
    }
}

//Step 2: create a test class
import java.util.Date;

public class ceShiThread1 {
    public static void main(String[] args){
        //1. Create a custom thread
        Thread1 thread = new Thread1();
        thread.start();
        //2. Main thread cyclic printing
        for (int i=0; i<10; i++){
            System.out.println("main Main thread executing:"+new Date().getTime());
        }
    }
}

The implementation effect is as follows:

 

 

Method 2: implement Runnable interface

Define subclasses to implement the Runnable interface.

Override the run method in the Runnable interface in the subclass.

Create a Thread object through the Thread class argument constructor.

Pass the subclass object of the Runnable interface as an actual parameter to the constructor of the Thread class.

Call the start method of Thread class: start the Thread and call the run method of Runnable subclass interface.

//Step 1: create a custom class to implement the Runnable interface
import java.util.Date;

public class Runnable2 implements Runnable{
    public void run() {
        for (int i=0; i<10; i++){
            System.out.println("MyRunnable Thread executing:"+new Date().getTime());
        }
    }
}

//Step 2: create a test class
import java.util.Date;

public class ceShiRunnable {
    public static void main(String[] args){
        //1. Create a custom thread
        Thread thread = new Thread(new Runnable2());
        thread.start();
        //2. Main thread cyclic printing
        for (int i=0; i<10; i++){
            System.out.println("main Main thread executing:"+new Date().getTime());
        }
    }
}

The implementation effect is as follows:

 

Method 3: implement Callable interface

You can cancel the execution results of specific Runnable and Callable tasks, query whether they are completed, obtain results, etc.

FutrueTask is the only implementation class of Futrue interface

FutureTask also implements Runnable and Future interfaces. It can be executed by the thread as Runnable or get the return value of Callable as Future

Introduction to FutureTask

Callable needs to use FutureTask class to help execute. FutureTask class structure is as follows:

Future interface:

Judge whether the task is completed: isDone()

Able to interrupt tasks: cancel()

Can get task execution results: get()

 

//Step 1: create a custom class to implement the Callable interface
import java.util.Date;
import java.util.concurrent.Callable;

public class Callable3 implements Callable<String> {
    public String call() throws Exception {
        for (int i=0; i<10; i++){
            System.out.println("MyCallable Executing:"+new Date().getTime());
        }
        return "MyCallable Execution complete!";
    }
}

//Step 2: create a test class
import java.util.Date;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ceShiCallable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask task = new FutureTask(new Callable3());
        Thread thread = new Thread(task);
        thread.start();

        for (int i=0; i<10; i++){
            System.out.println("main Main thread executing:"+new Date().getTime());
        }
        System.out.println(task.get());
    }
}

The operation effect diagram is as follows:

  

Mode 4: use thread pool

JDK 5.0 provides thread pool related API s: ExecutorService and Executors

ExecutorService: the real thread pool interface. Common subclass ThreadPoolExecutor

void execute(Runnable command): executes a task / command without a return value. It is generally used to execute Runnable

Future submit(Callable task): execute a task with a return value, and generally execute a Callable task

void shutdown(): closes the connection pool

Thread pool line class diagram

 

Executor interface: declares the execute(Runnable runnable) method to execute task code

ExecutorService interface: inherits the Executor interface and declares methods such as submit, invokeAll, invokeAny and shutDown

AbstractExecutorService abstract class: implements the ExecutorService interface and basically implements all the methods declared in ExecutorService

ScheduledExecutorService interface: inherits the ExecutorService interface and declares the scheduled task execution method

ThreadPoolExecutor class: inherits AbstractExecutorService and implements execute, submit, shutdown and shutdown now methods

ScheduledThreadPoolExecutor class: inherits the ThreadPoolExecutor class, implements the ScheduledExecutorService interface and implements its methods

Executors class: provides a method to quickly create a thread pool

//Step 1: create a custom class to implement the Runnable interface
import java.util.Date;

public class XianChengChi implements Runnable{
    public void run() {
        for (int i=0; i<10; i++){
            System.out.println("MyRunnable Thread executing:"+new Date().getTime());
        }
    }
}

//Step 2: create a test class
import java.util.Date;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ceShiXianChengChi {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //1. Create thread pool using Executors
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        //2. Execute threads through thread pool
        executorService.execute(new XianChengChi());
        //3. Main thread cyclic printing
        for (int i=0; i<10; i++){
            System.out.println("main Main thread executing:"+new Date().getTime());
        }
    }
}

Operation results:

 

Summary

Comparison between implementing interface and inheriting Thread class

Interface is more suitable for multiple threads of the same program code to share the same resource.

Interface can avoid the limitation of singleton inheritance in java.

The interface code can be shared by multiple threads, and the code and thread are independent.

The Thread pool can only put threads that implement the Runable or Callable interface, not directly into classes that inherit Thread.

Expansion:

In java, at least 2 threads are started each time the program runs. One is the main thread and the other is the garbage collection thread.

Comparison of Runnable and Callable interfaces

Similarities:

  1. Both are interfaces;
  1. Both can be used to write multithreaded programs;
  1. Both require a call to thread Start() starts the thread;

difference:

  1. The thread implementing the Callable interface can return the execution result; The thread implementing the Runnable interface cannot return results;
  1. The call() method of the Callable interface allows exceptions to be thrown; Exception throwing is not allowed in the run() method of the Runnable interface;
  1. Threads that implement the Callable interface can call future Cancel cancels execution, while the thread implementing the Runnable interface cannot

Note:

The Callable interface supports the return of execution results, and futuretask.com needs to be called at this time The get () method is implemented. This method will block the main thread until the 'future' result is obtained; When this method is not called, the main thread will not block!

3, Thread life cycle

 

newly build

New keyword creates a thread, which is in the new state

The JVM allocates memory for threads and initializes member variable values

be ready

When the thread object calls the start() method, the thread is ready

The JVM creates method stacks and program counters for threads and waits for the thread scheduler to schedule them

function

The ready thread obtains CPU resources and starts running the run() method. The thread enters the running state

block

When the following occurs, the thread will enter the blocking state

The thread calls the sleep() method to actively give up the occupied processor resources

The thread called a blocking IO method, and the thread was blocked before the method returned

A thread attempted to acquire a synchronization lock (synchronization monitor), but the synchronization lock is being held by another thread.

The thread is waiting for a notification

The program called the thread's suspend() method to suspend the thread. However, this method is easy to cause deadlock, so it should be avoided as far as possible

death

The thread will end in the following three ways, and then it will be in the dead state:

The execution of the run() or call() method is completed, and the thread ends normally.

The thread throws an uncapped Exception or Error.

Call the thread stop() method to end the thread. This method is easy to cause deadlock and is not recommended.

The life cycle body of a thread can be divided into the following five main stages

NEW

RUNNABLE

RUNNING

BLOCKED

TERMINATED

NEW status of thread

When we create a Thread object with the keyword new, it is not in the execution state at this time. Because the start method is not called to start the Thread, the Thread state is new. To be exact, it is only the state of the Thread object, because the current Thread does not exist before start, It's no different from creating an ordinary java object with the keyword new.

The New state enters the RUNNABLE state through the start method

The RUNNABLE state of the thread

When the thread object enters the RUNNABLE state, it must call the start method. At this time, a thread is really created in the JVM process. Once the thread is started, it will not be executed immediately. The operation of the thread is subject to the scheduling of the CPU like the process. Therefore, this intermediate state is called the executable state, that is, the RUNNABLE state, In other words, it is qualified to execute, but it is not really executed, but waiting for CPU scheduling

Due to the existence of Running state, it will not directly enter the BLOCKED state and TERMINATED state. Even if it calls wait, sleep or block io operation in the execution logic of the thread, it must first get CPU's dispatching execution power. Strictly speaking, the RUNNABLE line can only be terminated unexpectedly or entered RUNNING state.

RUNNING state of the thread

Some people often think that there is a running state missing from the Java thread state, which actually confuses two different levels of state. For the Java thread state, there is no so-called running state, and its runnable state includes the running state.

BLOCKED status of thread

Threads can switch to the following states in the BLOCKED state:

 directly enter the TERMINATED state, such as calling the stop method that JDK does not recommend or accidental death (JVM Crash)

 the thread blocking operation ends, such as reading the desired data bytes and entering the RUNNABLE state

 the thread has completed the sleep for the specified time and entered the RUNNABLE state

 the thread in the Wait is awakened by other threads notify/notifyall and enters the RUNNABLE state

 the thread obtains a lock resource and enters the RUNNABLE state

 the thread is interrupted during blocking. For example, other threads call the interrupt method and enter the RUNNABLE state

The TERMINATED state of the thread

TERMINATED is the final state of a thread. In this state, the thread will not switch to any other state. The thread will enter the TERMINATED state, which means that the whole life cycle of the thread is over. The following conditions will make the thread enter the TERMINATED state

 the thread runs normally and ends the life cycle

 thread running error, unexpected end

 JVM Crash causes all threads to end

IV. thread safety

What is thread safety

When multiple threads share the same global variable or static variable to write at the same time, data conflict may occur, that is, thread safety.

There will be no data conflict when reading.

If multiple threads run the same class that implements the Runnable interface at the same time, the result of each program run is the same as that of a single thread, and the values of other variables are the same as expected, which is thread safe; On the contrary, it is thread unsafe.

Thread safety issues:

In the computer system, the unit of system resource allocation is called process. In the same process, the computer allows multiple threads to execute concurrently, and multiple threads can share resources within the process, such as memory address, etc. However, when multiple threads access the same memory address concurrently and the value saved by the memory address is variable, thread safety problems may occur. Therefore, memory data sharing mechanism is needed to ensure thread safety.

Causes of thread safety problems:

Thread safety problems are caused by global variables and static variables.

If there are only read operations and no write operations on global variables and static variables in each thread, generally speaking, this global variable is thread safe; If multiple threads execute write operations at the same time, thread synchronization generally needs to be considered, otherwise thread safety may be affected.

Problem presentation:

In order to demonstrate thread safety, we use multithreading to simulate multiple windows and sell movie tickets for "Monkey King vs. Superman" at the same time.

//Step 1: create ticket thread class
public class Ticket implements Runnable{
    private int ticktNum = 100;

    public void run() {
        while(true){
            if(ticktNum > 0){
                //1. Simulate ticket issuing time
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //2. Print the process number and ticket number, and the number of votes shall be reduced by 1
                String name = Thread.currentThread().getName();
                System.out.println("thread "+name+"sell ticket:"+ticktNum--);
            }
        }
    }
}


//Step 2: create a test class
public class ceShiTicket {
    public static void main(String[] args){
        Ticket ticket = new Ticket();
        Thread thread1 = new Thread(ticket, "Window 1");
        Thread thread2 = new Thread(ticket, "Window 2");
        Thread thread3 = new Thread(ticket, "Window 3");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

Operation results:

 

There are two problems with the program:

1. The same number of votes, such as 5, was sold twice.

2. Nonexistent votes, such as 0 votes and - 1 votes, do not exist.

problem analysis

Thread safety problems are caused by global variables and static variables.

If each thread is read-only and does not write to global variables and static variables, generally speaking, this variable is thread safe;

If multiple threads execute write operations at the same time, thread synchronization generally needs to be considered, otherwise thread safety may be affected.

 

To sum up, the root cause of thread safety problem is:

Data shared by multiple threads in operation;

There are multiple thread codes for operating shared data;

Multiple threads have write operations on shared data;

Problem solving - thread synchronization

To solve the above thread problems, as long as a thread modifies a shared resource, other threads cannot modify the resource. After the modification is completed and synchronized, they can grab CPU resources and complete the corresponding operations, so as to ensure the synchronization of data and solve the phenomenon of thread insecurity.  

In order to ensure that each thread can normally perform shared resource operations, Java introduces seven thread synchronization mechanisms.

  1. synchronized code block
  2. synchronized method
  3. ReenreantLock
  4. Special domain variable (volatile)
  5. Local variable (ThreadLocal)
  6. LinkedBlockingQueue
  7. Atomic variable (atomic *)

Thread safety problem solution

Methods to solve the security problem of multithreading concurrent access to resources: 7

(1)synchronized

The synchronized keyword is used to control thread synchronization to ensure that our threads are not executed by multiple threads at the same time in a multithreaded environment and ensure the integrity of our data. The usage method is generally added to the method.

(2)Lock

Lock is in Java 1 6 was introduced. The introduction of lock makes the lock operable. It means that we manually obtain and release the lock when necessary. Synchronized is more convenient to use than lock. (we now recommend using synchronized).

1. synchronized code block

Sync code block:

The synchronized keyword can be used in a block in a method to indicate that only the resources of this block are mutually exclusive.

Syntax:

Synchronized{

Code that requires synchronization

}

Synchronous lock:

The synchronization lock of an object is just a concept, which can be imagined as marking a lock on the object

  1. The lock object can be of any type.
  2. Multiple threads use the same lock.

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).

The synchronization code block used is as follows:

public class Ticket2 implements Runnable{
    private int ticktNum = 100;
    //Define lock object
    Object obj = new Object();
    public void run() {
        while(true) {
            synchronized (obj) {
                if (ticktNum > 0) {
                    //1. Simulate ticket issuing time
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //2. Print the process number and ticket number, and the number of votes shall be reduced by 1
                    String name = Thread.currentThread().getName();
                    System.out.println("thread " + name + "sell ticket:" + ticktNum--);
                }
            }
        }
    }
}

The execution results are as follows: the thread safety problem is solved

 

2. synchronized method

Synchronization method:

The method modified with synchronized is called synchronous method, which ensures that when thread A executes this method, other threads can only wait outside the method.

Format:

public synchronized void method(){

Code that may cause thread safety problems

}

Who is the synchronization lock?

  1. For non static methods, the synchronization lock is this.
  2. For static methods, the synchronization lock is the bytecode object (class name. class) of the class where the current method is located.

The synchronization method code is as follows:

public class Ticket3 implements Runnable{
    private int ticktNum = 100;
    //Define lock object
    Object obj = new Object();
    public void run() {
        while(true) {
            sellTicket();
        }
    }

    private synchronized void sellTicket() {
            if (ticktNum > 0) {
                //1. Simulate ticket issuing time
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //2. Print the process number and ticket number, and the number of votes shall be reduced by 1
                String name = Thread.currentThread().getName();
                System.out.println("thread " + name + "sell ticket:" + ticktNum--);
            }
    }
}

The execution results are as follows: the thread safety problem is solved.

 3. ReenreantLock

Synchronous lock:

java.util.concurrent.locks.Lock mechanism provides a wider range of locking operations than synchronized code blocks and synchronized methods. Synchronized code blocks / synchronized methods have the functions of lock. In addition, lock is more powerful and object-oriented.

Synchronization lock method:

public void lock(): add synchronization lock.

public void. unlock(): release the synchronization lock.

The code of synchronization lock is as follows:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//7.4. 3. ReenreantLock
public class Ticket4 implements Runnable {
    private int ticktNum = 100;
    //Define the lock object: the constructor parameter is whether the thread obtains the lock fairly. true - fair; False - unfair, that is, it is monopolized by a thread. The default is false
    Lock lock = new ReentrantLock(true);

    public void run() {
        while (true) {
            lock.lock();
            try {//Lock
                if (ticktNum > 0) {
                    //1. Simulate ticket issuing time
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //2. Print the process number and ticket number, and the number of votes shall be reduced by 1
                    String name = Thread.currentThread().getName();
                    System.out.println("thread " + name + "sell ticket:" + ticktNum--);
                }
            } finally {
                //Release lock
                lock.unlock();
            }
        }
    }
}

The implementation effect is as follows:

Summary

The difference between Synchronized and Lock

  1. synchronized is a java built-in keyword. At the jvm level, Lock is a java class;
  2. synchronized cannot judge whether to obtain the Lock status, and Lock can judge whether to obtain the Lock;
  3. synchronized will automatically release the lock (a} thread will release the lock after executing the synchronization code; b thread will release the lock if an exception occurs during execution). Lock needs to release the lock manually in finally (unlock() method releases the lock), otherwise it is easy to cause thread deadlock;
  4. Two threads 1 and 2 with the synchronized keyword. If the current thread 1 obtains a Lock, the thread 2 waits. If thread 1 is blocked, thread 2 will wait all the time, but the Lock lock does not necessarily wait. If you try to obtain the Lock, the thread can end without waiting all the time;
  5. synchronized locks are reentrant, non interruptible and unfair, while Lock locks are reentrant, judgmental and fair (both)
  6. Lock lock is suitable for the synchronization of a large number of synchronized codes, and synchronized lock is suitable for the synchronization of a small number of codes.

V. thread deadlock

What is deadlock

Multithreading and multiprocessing improve the utilization of system resources and improve the processing capacity of the system. However, concurrent execution also brings a new problem deadlock.

Deadlock refers to a deadlock (waiting for each other) caused by multiple threads competing for resources. Without external force, these processes will not be able to move forward.

 

Deadlock necessary condition

Four necessary conditions for java deadlock generation:

The following four conditions are necessary for deadlock. As long as the system deadlock occurs, these conditions must be true, and as long as one of the above conditions is not met, deadlock will not occur.

1. Mutually exclusive use, that is, when a resource is used (occupied) by one thread, other threads cannot use it

mutual exclusion

The process requires exclusive control over the allocated resources (such as printers), that is, a resource is occupied by only one process for a period of time. At this time, if other processes request the resource, the requesting process can only wait.

2. It cannot be preempted. The resource requester cannot forcibly seize resources from the resource owner, and resources can only be released by the resource owner.

Inalienable conditions

The resource obtained by a process cannot be forcibly taken away by other processes before it is used up, that is, it can only be released by the process that obtains the resource itself (it can only be released actively).

3. Request and hold, that is, when the resource requester requests other resources while maintaining the possession of the original resources.

Request and hold conditions

The process has maintained at least one resource, but has made a new resource request, and the resource has been occupied by other processes. At this time, the requesting process is blocked, but it does not let go of the resources it has obtained.

4. Cyclic waiting, that is, there is a waiting queue: P1 occupies P2 resources, P2 occupies P3 resources, and P3 occupies P1 resources. This forms a waiting loop.

Cycle waiting condition

There is a circular waiting chain of process resources. The resources obtained by each process in the chain are requested by the next process in the chain at the same time. That is, there is a waiting process set {Pl, P2,..., Pn}, in which the resources waiting for Pi are occupied by P(i+1) (i=0, 1,..., n-1), and the resources waiting for Pn are occupied by P0, as shown in the figure.

 

Deadlock sample code

public class SiSuo implements Runnable{
    private static Object obj1 = new Object();//Defined as a static variable so that threads can share instances
    private static Object obj2 = new Object();//Defined as a static variable so that threads can share instances
    public int flag = 0;
    public void run() {
        if(flag == 0){
            System.out.println("flag: "+flag);
            synchronized (obj1){
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj2){
                    System.out.println("flag: "+flag);
                }
            }
        }
        if(flag == 1){
            System.out.println("flag: "+flag);
            synchronized (obj2){
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj1){
                    System.out.println("flag: "+flag);
                }
            }
        }
    }

}


public class ceShiSiSuo {
    public static void main(String[] args){
        SiSuo deadLock1 = new SiSuo();
        SiSuo deadLock2 = new SiSuo();
        deadLock2.flag = 1;

        Thread thread1 = new Thread(deadLock1);
        Thread thread2 = new Thread(deadLock2);

        thread1.start();
        thread2.start();
    }

}

The execution effect is as follows: only two flag values are printed, indicating that a deadlock occurs

Deadlock handling

  1. Deadlock prevention: set some restrictions to destroy one or more of the four necessary conditions to prevent deadlock.
  2. Avoid deadlock: during the dynamic allocation of resources, some method is used to prevent the system from entering an unsafe state, so as to avoid deadlock.
  3. Deadlock detection: the system is allowed to have a deadlock during operation, but a detection mechanism can be set to detect the deadlock in time and take appropriate measures to remove it.
  4. Release Deadlock: when a deadlock is detected, appropriate measures will be taken to release the process from the deadlock state.

Deadlock prevention

Deadlock prevention is to try to destroy at least one of the four necessary conditions for deadlock, and strictly prevent the occurrence of deadlock.

 1. Break the "mutually exclusive" condition

The "mutually exclusive" condition cannot be broken. Therefore, in deadlock prevention, it mainly destroys several other necessary conditions, rather than destroying the "mutually exclusive" condition.

2. Destroy the "occupy and wait" condition

Breaking the "occupy and wait" condition means that the process is not allowed to apply for other resources when it has obtained some resources in the system. That is to think of a way to prevent the process from applying for other resources while holding resources.

Method 1: allocate resources at one time, that is, when creating a process, ask it to apply for all the resources required, the system may meet all its requirements, or give it nothing.

Method 2: require each process to release its resources before making a new resource application. In this way, when a process needs resource S, it must first release the resource R it previously occupied before it can apply for S, even if it may use resource R soon.

3. Destroy the "non preemptive" condition

Destroying the "non preemptive" condition is to allow the preemption of resources.

Method 1: if a process occupying some resources makes a further resource request and is rejected, the process must release the resources it originally occupies. If necessary, it can request these resources and other resources again.

Method 2: if a process requests a resource currently occupied by another process, the operating system can preempt another process and ask it to release resources. Method 2 can prevent deadlock only when the priorities of any two processes are different.

4. Destroy the "cycle waiting" condition

One way to break the "cycle waiting" condition is to uniformly number all resources in the system. The process can apply for resources at any time, but all applications must be made according to the numbering order (ascending order) of resources. This can ensure that the system does not deadlock.

Deadlock avoidance

Deadlock avoidance does not strictly limit the existence of the necessary conditions for deadlock, because even if the necessary conditions for deadlock exist, deadlock does not necessarily occur.

Orderly resource allocation method

The implementation steps of the algorithm are as follows:

  1. All resources must be numbered uniformly, such as printer 1, fax 2, disk 3, etc
  2. Similar resources must be applied at one time. For example, printers and fax machines are generally the same machine, and they must be applied at the same time
  3. Different types of resources must be applied in order

For example, there are two processes P1 and P2 and two resources R1 and R2

P1 request resources: R1, R2

P2 requested resources: R1, R2

This destroys the loop conditions and avoids deadlock.

Banker Algorithm

Banker Algorithm (Banker's Algorithm) is a famous algorithm to avoid Deadlock. It is an algorithm to avoid Deadlock designed for T.H.E system by ezger dijestra in 1965. It judges and ensures the safe operation of the system based on the allocation strategy of bank lending system. The flow chart is as follows:

 

The basic idea of banker algorithm is to judge whether the system is safe before allocating resources; If yes, it will be allocated. It is the most representative deadlock Algorithm.

If process I requests REQUEST [i], the banker algorithm will judge according to the following rules.

1. If request [i] < = need [I, j], turn to (2); Otherwise, an error occurs.

2. If request [i] < = available [i], turn to (3); Otherwise, wait.

3. The system tries to allocate resources and modify relevant data:

AVAILABLE[i]-=REQUEST[i];// Available resources - requested resources

ALLOCATION[i]+=REQUEST[i];// Allocated resources + requested resources

NEED[i]-=REQUEST[i];// Number of resources required - number of resources requested

4. The system performs safety inspection. If it is safe, the allocation is established; Otherwise, the trial allocation will be invalidated, the system will be restored to its original state, and the process will wait.

Sequential locking

When multiple threads need the same locks, but lock in different order, deadlock is easy to occur.

For example, the following two threads will Deadlock:

Thread 1:

lock A (when C locked)

lock B (when C locked)

wait for C

Thread 2:

wait for A

wait for B

lock C (when A locked)

If you can ensure that all threads obtain locks in the same order, deadlock will not occur. For example, the following two threads will not deadlock

Thread 1:

lock A

lock B

lock C

Thread 2:

wait for A

wait for B

wait for C

Sequential locking is an effective deadlock prevention mechanism. However, this method needs to know all locks that may be used in advance, but sometimes it is unpredictable, so this method is only suitable for specific scenarios.

Time limited locking

Time limited locking is that a thread adds a timeout time when trying to obtain a lock. If it exceeds this time, it will give up the lock request, go back and release all the obtained locks, and then wait for a random period of time to try again

The following is an example showing a scenario in which two threads attempt to acquire the same two locks in different order, and then go back and retry after a timeout occurs:

Thread 1 locks A

Thread 2 locks B

Thread 1 attempts to lock B but is blocked

Thread 2 attempts to lock A but is blocked

Thread 1's lock attempt on B times out

Thread 1 backs up and releases A as well

Thread 1 waits randomly (e.g. 257 millis) before retrying.

Thread 2's lock attempt on A times out

Thread 2 backs up and releases B as well

Thread 2 waits randomly (e.g. 43 millis) before retrying.

In the above example, thread 2 retries locking 200 milliseconds earlier than thread 1, so it can successfully obtain two locks first. At this time, thread 1 attempts to acquire lock A and is in A waiting state. When thread 2 ends, thread 1 can also successfully obtain these two locks.

This approach has two disadvantages:

  1. When the number of threads is small, this method can avoid deadlock, but when the number of threads is too large, the probability of the same locking time limit of these threads is much higher, which may lead to an endless loop of retry after timeout.
  2. Timeout cannot be set for synchronized synchronization blocks in Java. You need to create a custom lock or use Java. Net in Java 5 util. Tools under concurrent package.

Deadlock detection

To prevent and avoid deadlock, the system overhead is large and resources cannot be fully utilized. The better way is not to take any restrictive measures, but to provide means to detect and release deadlock, which is deadlock detection and recovery.

Deadlock detection data structure:

  1. E is the existing resource vector, the total number of existing resources of each code type
  2. A is the available resource vector, and Ai represents the number of resources currently available (i.e. resources not allocated)
  3. C is the current allocation matrix. Line i of C represents the number of resources of each type currently held by Pi
  4. R is the request matrix, and each row of R represents the number of resources required by P

 

Deadlock detection steps:

  1. Find A process Pi without an end tag for which the i-th row vector of the R matrix is less than or equal to A.
  2. If such A process is found, execute the process, then add the i-th row vector of the C matrix to A, mark the process, and go to step 1
  3. If there is no such process, the algorithm terminates
  4. At the end of the algorithm, all unmarked processes are deadlock processes.

Deadlock recovery

Use preemptive recovery.

Temporarily transfer a resource from its current process to another process.

This approach is likely to require manual intervention, and the feasibility of the main approach depends on the characteristics of the resources themselves.

Recovery with rollback

Periodically back up the state of the process. When a process deadlock is found, reset the process to an earlier state without obtaining the required resources according to the backup, and then allocate these resources to other deadlock processes.

Recovery by killing processes

The most direct and simple way is to kill one or more processes.

Try to ensure that the killing process can start from scratch without side effects.

6, Thread communication

1. Why thread communication

When multiple threads execute concurrently, the CPU switches threads randomly by default. Sometimes we want the CPU to execute threads according to our rules. At this time, coordinated communication between threads is required.

2. Thread communication mode

Common methods of inter thread communication are as follows:

3. Sleep wake-up mode:

wait, notify, notifyAll of Object

await, signal, signalAll of Condition

  1. CountDownLatch: used for A thread A to wait for several other threads to execute before it executes
  2. CyclicBarrier: a group of threads wait to a certain state and then execute all at the same time
  3. Semaphore: used to control access to a group of resources

4. Sleep wake-up mode

wait, notify, notifyAll of Object

public class XiuMian {
    private Object obj = new Object();
    private Integer i=0;
    public void odd() {
        while(i<10){
            synchronized (obj){
                if(i%2 == 1){
                    System.out.println("Odd:"+i);
                    i++;
                    obj.notify();
                } else {
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public void even(){
        while(i<10){
            synchronized (obj){
                if(i%2 == 0){
                    System.out.println("even numbers:"+i);
                    i++;
                    obj.notify();
                } else {
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    public static void main(String[] args){
        final XiuMian runnable = new XiuMian();
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                runnable.odd();
            }
        }, "Even thread");
        Thread t2 = new Thread(new Runnable() {
            public void run() {
                runnable.even();
            }
        }, "Odd thread");

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

await, signal, signalAll of Condition

/*Object Difference between Condition sleep wake-up
          object wait()Must be used under synchronized,
          object wait()You must wake up through the Nodify() method
          condition await() Must be used with Lock (mutex / shared Lock)
          condition await() Wake up must be performed through the signal() method
*/
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class XiuMian2 {
    //private Object obj = new Object();
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    private Integer i=0;
    public void odd() {
        while(i<10){
            lock.lock();
            try{
                //synchronized (obj){
                if(i%2 == 1){
                    System.out.println("Odd:"+i);
                    i++;
                    //obj.notify();
                    condition.signal();
                } else {
//                    try {
//                        obj.wait();
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
                    condition.await();
                    }
                }catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }

    public void even(){
        while(i<10){
            lock.lock();
            try{
                if(i%2 == 0){
                    System.out.println("even numbers:"+i);
                    i++;
                    condition.signal();
                } else {
                    condition.await();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
    public static void main(String[] args){
        final XiuMian2 runnable = new XiuMian2();
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                runnable.odd();
            }
        }, "Even thread");
        Thread t2 = new Thread(new Runnable() {
            public void run() {
                runnable.even();
            }
        }, "Odd thread");

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

Difference between Object and Condition sleep wake-up

  1. object wait() must be used under synchronized,
  2. object wait() must wake up through the Nodify() method
  3. condition await() must be used with Lock (mutex / shared Lock)
  4. condition await() must wake up through the signal() method

CountDownLatch mode

CountDownLatch is in java1 5 was introduced and exists in Java util. Under concurrent package.

CountDownLatch enables a thread to wait for other threads to complete their work before executing.

CountDownLatch is implemented through a counter. The initial value of the counter is the number of threads.

Every time a thread completes its task, the value of the counter will decrease by 1. When the counter value reaches 0, it indicates that all threads have completed the task, and then the threads waiting on the lock can resume executing the task.

Example code:

 

/*9.2.2.   CountDownLatch mode
CountDownLatch It's in Java 1 5 was introduced and exists in Java util. Under concurrent package.
CountDownLatch This class enables a thread to wait for other threads to complete their work before executing.
CountDownLatch It is implemented through a counter. The initial value of the counter is the number of threads
*/
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class XiuMian3 {
    private Integer i = 0;
    private CountDownLatch countDownLatch = new CountDownLatch(1);

    public void odd(){
        while(i < 10){
            if(i%2 == 1){
                System.out.println("Odd:"+i);
                i++;
                countDownLatch.countDown();
            } else {
                try {
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void even(){
        while(i < 10){
            if(i%2 == 0){
                System.out.println("even numbers:"+i);
                i++;
                countDownLatch.countDown();
            } else {
                try {
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public static void main(String[] args){
        final XiuMian3 countDown = new XiuMian3();
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                countDown.odd();
            }
        },"Odd number");
        Thread t2 = new Thread(new Runnable() {
            public void run() {
                countDown.even();
            }
        },"even numbers");

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

CyclicBarrier mode

CyclicBarrier is in Java 1 5 was introduced and exists in Java util. Under concurrent package.

The CyclicBarrier implementation lets a group of threads wait to a certain state and then execute all at the same time.

The bottom layer of CyclicBarrier is

Three threads are started at the same time. The example code is as follows:

/*9.2.3.   CyclicBarrier mode
CyclicBarrier It's in Java 1 5 was introduced and exists in Java util. Under concurrent package.
CyclicBarrier Implementation allows a group of threads to wait to a certain state and then execute all at the same time.
CyclicBarrier The bottom is
 Three threads are started at the same time. The example code is as follows:
*/
import java.util.Date;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;

public class XiuMian4 {
    public static void main(String[] args){
        final CyclicBarrier cyclicBarrier = new CyclicBarrier(3);

        new Thread(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName()+": prepare...");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"Start up complete:"+new Date().getTime());
            }
        },"Thread 1").start();
        new Thread(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName()+": prepare...");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"Start up completed:"+new Date().getTime());
            }
        },"Thread 2").start();
        new Thread(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName()+": prepare...");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"Start up completed:"+new Date().getTime());
            }
        },"Thread 3").start();
    }
}

Semaphore mode

Semaphore is in Java 1 5 was introduced and exists in Java util. Under concurrent package.

Semaphore is used to control access to a group of resources.

Workers work with machines, and the example code is as follows:

/*9.2.3.   9.2.4. Semaphore mode
Semaphore It's in Java 1 5 was introduced and exists in Java util. Under concurrent package.
Semaphore Used to control access to a group of resources.
Workers work with machines, and the example code is as follows:
*/
import java.util.concurrent.Semaphore;

public class XiuMian5 {
    static class Machine implements Runnable{
        private int num;
        private Semaphore semaphore;

        public Machine(int num, Semaphore semaphore) {
            this.num = num;
            this.semaphore = semaphore;
        }

        public void run() {
            try {
                semaphore.acquire();//Request machine
                System.out.println("worker"+this.num+"Request machine, machine in use");
                Thread.sleep(1000);
                System.out.println("worker"+this.num+"After use, the machine has been released");
                semaphore.release();//Release the machine
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args){
        int worker = 8;//Number of workers
        Semaphore semaphore = new Semaphore(3);//Number of machines
        for (int i=0; i< worker; i++){
            new Thread(new Machine(i, semaphore)).start();
        }
    }
}

Summary

The difference between sleep and wait

 

Difference between wait and notify

wait and notify are both methods in Object

Both wait and notify threads must obtain object locks before execution

The function of wait is to make the current thread wait

The function of notify is to notify other threads waiting for the object lock of the current thread

Topics: Java Eclipse intellij-idea