Java multithreading mechanism:
Basic concept of threads
Program: is an executable static code
Process: A dynamic loading process of a program (when the program is loaded into memory, the program is converted to a process)
Thread: a process can be further refined into a thread, which is an execution path within a program. A thread cannot exist independently, but must be attached to a process
For example: turn on tinder. Tinder exists as a process in memory, which contains two threads for virus killing and garbage cleaning
A Java application with at least three threads:
Main thread, garbage collection thread, exception handling thread
Multiple threads share the process’s heap and method area resources, but each thread has its own program counter, virtual machine stack, and local method stack
The function of the program counter:
1. Control the process of code execution
2. Record the execution location of the current thread
The main purpose of keeping program counters private is to restore them to the correct execution location after a thread switch
Stack and local method stack function:
The stack is used to store information such as local variable tables
Stack and local methods are stack private to ensure that local variables in a thread are not accessed by other threads
The heap is used to hold newly created objects, and the method area is used to hold loaded class information, which is shared by each thread
Advantages of using multithreading
The advantage is to make full use of the CPU idle time slice, use as little time as possible to respond to user requirements, making the overall process running efficiency is greatly improved, while enhancing the flexibility of the application.
If it is single-threaded, it can handle only one user request at a time, while multi-threaded can handle multiple user requests simultaneously
Context switch
Context switch is when one thread of work is suspended by another thread, and the other thread occupies the processor and starts executing the task
The CPU implements this mechanism by allocating a slice of CPU time to each thread. The time slice is the amount of time that the CPU allocates to each thread. Because the time slice is so short, the CPU keeps switching between threads to give us the impression that multiple threads are executing simultaneously.
Multithreaded creation
1. Inherit the Thread class
Implementation steps:
Create a subclass that extends Thread
Override the run() of the Thread class to declare the action the Thread needs to perform in run()
Create an object subclass of Thread
Call start() from this object
Start () function: start the current thread to call the current thread run
2. Implement the Runnable interface
Implementation steps:
Create a class that implements the Runnable interface
Implementation class to implement Runnable’s abstract method: run()
Create an object that implements the class
Create the Thread object by passing this object as a parameter to the constructor of the Thread class
Public Thread(Runnable Target) public Thread(Runnable target)Copy the code
Call start() from an object of class Thread
Comparison of the above two methods
1. The implementation interface has no single inheritance limitation
2. The Thread class needs to use static to decorate shared data. Implementing Runnable is to pass the implementation class as a parameter to Thread constructor
3. Implement the Callable interface
Implementing the Callable interface is more powerful than implementing the Runnable interface. We need to override the call method, which can throw exceptions
Implementation steps:
Create an implementation class that implements Callable.
Implement the call() method, declaring in call() what this thread needs to do.
Create an object for the Callable interface implementation class.
The object of this Callable interface implementation class is passed to the FutureTask constructor to create the object of the FutureTask.
Pass the object of FutureTask as a parameter to the constructor of the Thread class, create the Thread object, and call the start() method.
4. The thread pool
Advantages: A thread pool is a container for multiple threads that can be used repeatedly, eliminating the need to frequently create thread objects
Steps to use a thread object in a thread pool:
(1) Create a thread pool object
(2) Create a Runnable interface subclass object
(3) Submit the Runnable interface subclass object
(4) Close the thread pool
Thread pool method:
Executors: Create the factory class for the thread pool
Public static ExecutorService newFixedThreadPool(int nThreads) : Returns a thread pool object
ExecutorService: thread pool class
Future<? > submit(Runnable task) : Obtain a thread object from the thread pool and execute it
Public class ThreadPool implements Runnable {@override public void run() {system.out.println (" implements a thread "); }} public static void main (String [] args) {/ / create a thread pool objects ExecutorService service = Executors. NewFixedThreadPool (2); // Create a Runnable instance object ThreadPool t = new ThreadPool(); // get the thread object from the ThreadPool and call run() service.submit(t) in ThreadPool; // Call run() service.submit(t); // Note: After the submit method is called, the program does not terminate because the thread pool controls the thread closing. Return the used threads to the thread pool // shutdown the thread pool service.shutdown(); }Copy the code
The life cycle of the thread
The Thread is in the new state
2. Ready: The new thread is in the ready state when it executes the start method
3. Running: The running state is when a ready thread acquires CPU execution rights
4. Blocking: The running thread becomes blocked when it encounters a sleep wait condition, such as a synchronization lock
5. Death: The running thread becomes dead when the run or stop exception is encountered
Thread synchronization
First, introduce the concept of locking: when a method or block of code uses a lock, at most one thread is executing the code at any one time
1. Synchronize code blocks
1.1 Implementing the Runnable interface
Synchronized {// objects of any class can act as locks // Code that needs to be synchronized operates on methods that share data}Copy the code
private int ticket=100; @Override public void run() { while (true) { synchronized (this) { if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println (thread.currentThread ().getName() + "ticket =" + ticket); ticket--; } else { break; }}}}Copy the code
Multiple threads must share a lock
Since this is a Thread object instantiated with the same Runnable object, the current object is used as the lock
1.2 Inheriting the Thread class
Inheritance is basically the same as implementing interfaces, just make sure the locks are the same
The lock is set to static or the current class is used as the lock
2. Synchronization method
You can set the shared data operation method to the synchronization method
Public synchronized void show(){// synchronized void show();Copy the code
The non-static synchronization method synchronization monitor is this
The static synchronization method synchronization monitor is the current class itself
3. Add Lock Lock
Methods in lock
Locke () method: lock
The unlock() method: releases the lock
private ReentrantLock lock=new ReentrantLock(); The lock (); / / lock lock. // Unlock the lock. Lock. Unlock ();Copy the code
The differences between synchronized and lock:
Synchronized releases the lock automatically after executing the synchronized code
Lock need to manually start the lock, manually close the lock
The deadlock problem
A deadlock is when multiple processes are waiting for each other because they are competing for resources, and none of them can move forward without an external force
Four conditions necessary for deadlock to occur
- Mutually exclusive: Only one process can access the resource at a time
- No deprivation condition: The occupied resources can only be released by the owner and cannot be deprived by other processes
- Request and hold conditions: when a process is blocked by a request for a resource, it holds on to a resource it has acquired
- Circular wait condition: a chain of processes exists such that each process occupies at least one resource required by the next process
How to solve the deadlock problem:
Break one or more of the four conditions necessary for deadlock creation
- Break mutual exclusion: Allow multiple processes to access resources simultaneously
- Destroy not deprive: a resource that must be released to preserve
- Break request and hold: Allocate all resources at once
- Break loop wait: Define a linear order of resource types to prevent
Banker algorithm is a typical deadlock solution: banker algorithm
Thread communication
Wait (): Once this method is executed, the current thread is blocked and the lock is released
Notify (): Once this method is executed, a wait thread is awakened. The wait method releases the lock. The notify method does not release the lock
NotifyAll (): wakes up all wait threads
Wait (),notify(), and notifyAll() can only be executed in a synchronized block or method
All three methods are methods in Object
The differences between sleep and wait methods:
(1) Methods are declared in different positions. Sleep is a method in Thread and wait is a method in Object
(2) Wait can only be used in synchronized blocks or methods
(3) Wait releases the lock, while sleep does not
Producer consumer problem
Problem analysis: The system has a set of producer processes and a set of consumer processes,
The producer process puts a product into the buffer one at a time, and the consumer process takes a product from the buffer one at a time and uses it.
Code implementation:
Public class Resource {// Number of current resources int num=0; Int size=10; Public synchronized void remove(){while (num==0){try {system.out.println (" synchronized "); wait(); } catch (InterruptedException e) { e.printStackTrace(); } } num--; System.out.println(" consumer Thread is: "+ thread.currentThread ().getName()+" Number of resources "+num); notify(); } public synchronized void put(){while (num==size){try {system.out.println (" producerwait "); wait(); } catch (InterruptedException e) { e.printStackTrace(); } } num++; System.out.println(" producer Thread is: "+ thread.currentThread ().getName()+" Number of resources "+num); notify(); }}Copy the code
Public class Consumer implements Runnable {private Resource Resource; public class Consumer implements Runnable; public Consumer(Resource resource) { this.resource = resource; } @Override public void run() { while (true){ resource.remove(); }}}Copy the code
Public class Producer implements Runnable {private Resource Resource; public class Producer implements Runnable; public Producer(Resource resource) { this.resource = resource; } @Override public void run() { while (true){ resource.put(); }}}Copy the code
Thread-safe singleton mode
The singleton design pattern
Class Bank{private Bank(){} private static volatile Bank instance=null; public static Bank getInstance() { if (instance == null) { synchronized (Bank.class) { if (instance == null) { instance = new Bank(); } } } return instance; }}Copy the code
Analyze the code:
If two threads arrive at the synchronized block at the same time, then the instantiation code will be executed only once by the thread that grabbed the lock first. The thread that grabbed the lock later will find that instance is not null in the second if judgment, so it skips the statement that created the instance.
If (instance == null); if (instance == null); if (instance == null);
What the first if statement does is: when an object is already instantiated, the thread does not need to queue for another thread to release the lock, which is faster
What the second if statement does: keep the singleton pattern from breaking
In the Java memory model, the volatile keyword can be used either to guarantee visibility or to disallow instruction reordering. This is because instance = new Bank() is not an atomic operation. In fact, the above statement does at least three things in the JVM:
(1) Allocate memory space for instance
(2) initialize instance by calling the constructor of Bank, etc
(3) Point the instance object to the allocated memory space (after this step, instance is not null)
The 1-2-3 sequence needs to be noticed here, because there is an instruction reorder optimization, which means that the order of steps 2 and 3 is not guaranteed, and the final order of execution could be 1-2-3 or 1-3-2.
In 1-3-2, the sequence of program execution is as follows:
When volatile is used, reordering of related statements is somewhat prohibited