Multithreading in java
What is process?
A process is a task in the operating system. It is a memory area that contains some resources. A process can contain one or more execution units called threads. These threads can be considered to be executed at the same time (actually occupying CPU resources in turn and performing fast switching to achieve seemingly simultaneous execution ). Each process also has a private virtual address space, which can only be accessed by included threads. After the operating system creates a process, the process automatically applies for a thread named as the main thread.
What is thread?
A thread is a sequential execution stream of processes. Similar threads can share a piece of memory space and a group of system resources. The load of a thread during the switchover is very small. Therefore, the thread is also considered a lightweight process. A process can contain multiple groups of threads.
What is the difference between a process and a thread?
A process has at least one thread, and its division scale is smaller than that of the process. Why. Because each process has an independent memory unit during execution, the process consumes a lot of resources during switching. Multiple threads share the memory. During thread switching, the memory unit does not need to be switched, which greatly improves the running efficiency. Multithreading means that multiple execution parts in an application can be executed simultaneously. However, the operating system does not regard these threads as independent programs for process scheduling and resource allocation.
Concurrency and parallelism?
Multiple Threads are executed at the same time. In fact, the OS divides the CPU running time into many time slices. The thread that obtains the time slice executes and other threads wait. Because the switching speed is very fast, it is executed at the same time at the macro level, but not at the micro level. This is called concurrency. Parallel Execution is performed at the same time in both micro and macro aspects. Here, parallel execution programs can be implemented through multiple CPUs.
Create Thread Use Thread to create Thread and start Thread
It defines a specific Thread by inheriting the Thread class and rewriting the run method in the Thread class. The purpose of rewriting the run method is to define the logic to be executed by the thread. The start thread does not call the run method, but calls the start () method of the thread. The start () method includes threads in the thread scheduling sequence, which can be executed after the current thread obtains the time segment. A Thread class is a Thread class. Each instance represents a Thread that can run concurrently.
Class SampleThread extends Thread {public void run () {System. out. println (implemented by inheriting the Thread class to override the run method);} public static void main (String [] args) {SampleThread thread = new SampleThread (); thread. start ();}}
Use Runnable to create and start a thread
Implement the Runnable interface and override the run method to define the thread body. Then, when the thread is created, the Runnable instance is passed in and the thread is started. The advantage of doing so is to separate the defined thread from the task to be executed by the thread to reduce coupling. In addition, java is a single inheritance. If a class inherits the Thread class, it cannot inherit other classes. (The Thread class also implements the Runnable interface)
In the Runnable interface, only the abstract run method is defined:
public abstract void run();
Class SampleThread implements Runnable {public void run () {System. out. println (implemented by inheriting the Thread class to override the run method);} public static void main (String [] args) {SampleThread sample = new SampleThread (); thread thread = new Thread (sample); thread. start ();}}Use an anonymous internal class to create a thread
Thread thread = new Thread(){public void run() {};};
Interrupt thread
When the run method is executed, or no exception is caught, the thread is terminated. However, if you want to force terminate a thread, you can use the interrupt method to request to terminate the thread.
Thread interruption changes the thread interruption status. After interruption, the thread may die, wait for a new task, or continue to the next step. This depends on the program itself. The thread constantly checks the interrupt flag to determine whether it should be interrupted. However, if the thread is blocked and interrupt is used to terminate the thread, an InterruptedException exception is thrown.
Use isInterrupted to check whether the thread is interrupted
Boolean isInterrupted ()
Boolean interrupted (): checks whether the current thread is interrupted, but this call has side effects. It sets the thread interruption status to false.
Void interrupt (): sends an interrupt request. The interrupt status is set to true.
Thread status
The thread can be in 6 states:
New: New
Runnable: runable
Blocked: Blocked
Waiting: Waiting
Timed waiting: Timed wait
Terminated
The getState () method is used to detect the current thread status.
New thread
Use the new operator to create a Thread, such as new Thread (t), which has not yet started running. When a thread is in the newly created State, it indicates that the program has no code in the running thread.
Runable thread
Once the start () method is called, the thread is in the runnable state, and the running thread may be running or not. This depends on the running time provided by the operating system. In fact, interruption is used to get the running time of other threads.
Blocked thread and waiting thread
Blocked threads and waiting threads are temporarily inactive, do not run any code, and consume the least amount of resources. Until the thread scheduler reactivate it.
When a thread tries to obtain an internal object lock held by other threads, the thread enters the blocking state. When all other threads release the lock and the scheduler allows the thread to hold it, the thread becomes non-blocking.
When a thread waits for another thread to notify the scheduler of a condition, it enters the waiting state. Such as calling the Object. wait method or the Thread. join method, or waiting for Lock or Condition to be true.
Timing status
There are several methods that have a timeout parameter. Calling them causes the thread to enter the timing State until the timeout period expires or a proper notification is received. Thread. sleep (), Object. wait (), Thread. join (), Lock. tryLock (), Condition. await
Terminated thread
There are two reasons for thread termination:
The run method Exits normally and naturally dies.
The run method was terminated because no exception was caught and an unexpected death occurred.
Thread operation API to obtain thread Information
Thread. currentThread () method: gets the Thread of the currently running code segment
Thread thread = Thread.currentThread();
Boolean isAlive (): test whether the thread is active
Boolean isDaemon (): test whether the thread is a daemon thread.
Boolean isInterrupted (): test whether the thread has been interrupted.
Long getId (): gets the thread identifier
String getName (): name of the returned thread
Int getPriority (): returns the thread priority.
Thread. state getState (): gets the Thread state
Thread priority
Thread switching is controlled by thread scheduling. We cannot interfere with the code, but we can improve the thread priority to maximize the chance that the thread gets time slices.
The thread priority is divided into 10 levels, which are 1-10, of which 1 is the minimum and 10 is the maximum. The thread provides three constant values to indicate the lowest, highest, and default priority.
Thread. MIN_PRIORIITY
Thread. MAX_PRIORITY
Thread. NORMAL_PRIORITY
Set Through setPriority ()
Daemon thread
In java, threads are divided into two types: User Thread and Daemon Thread ). The Daemon thread facilitates the running of other threads. For example, the garbage collector is a Daemon thread. Daemon is no different from User Thread. The main difference is the departure of virtual machines. When all User threads run and exit, only Daemon Thread is left, and the virtual machine exits. The daemon process is not only available to virtual machines, but can also be set to setDaemon (boolean on)
Do not set the Daemon Thread easily, because you do not know when the User Thread will be completed. If the Daemon Thread you have defined has not been completed yet, it will also be forced to exit.
Sleep () method
Sleep (long mills) is used to block the current process. The blocking time is the specified number of milliseconds. After the blocking time expires, the system enters the Runable state and waits for the time slice to be allocated. This method throws InterruptedException during the Declaration. Therefore, you must capture this exception when using this method.
Yield () method
Static void yield (): takes the initiative to return the current time slice to the Runnable state, waiting for the allocation of time slice.
Join () method
It is used to wait for the end of the current thread. This method will throw InterruptedException.
Thread Synchronization
Concurrent thread security issues occur when multiple threads read and write the same critical resource at the same time. For example, when two threads read the same object and each thread modifies the state of the object, an error occurs. This situation is called a competitive condition.
To solve thread security problems, you need to change asynchronous operations to synchronous operations:
Asynchronous operations: multi-thread concurrent operations, each of which executes its own
Synchronization operation: There are sequential operations. I will not execute them if you execute them.
Lock Object
Java provides two mechanisms to prevent code blocks from interfering with concurrent access. One is familiar with the use of the synchronized keyword, and the other is the use of the ReentrantLock class.
ReentrantLock:
Lock lock = new ReentrantLock (); lock. lock (); try {// code block in the critical section. This ensures that only one thread enters the critical section at any time. Once a thread blocks the lock object, no other thread can use the lock statement // when the lock is called, they are blocked until the first thread releases the lock Object} finally {lock. unlock ();}
The unlock release lock object must be placed in finally. If the code in the critical section throws an exception, the finally lock object can be released; otherwise, other threads will always be blocked.
Each class object has its own ReentrantLock () object. If two threads access the same class object, a serial service is provided. But when accessing different objects, each thread has a different lock object, and the two threads will not be congested. Operations on different instances are not affected among threads.
Condition object
If a thread enters the critical section and finds that a condition is required to run, the thread has obtained the lock object but cannot run it down. in java, the condition object (condition variable) is used).
A lock object can have multiple condition objects. The newCondition () method is used to obtain the condition object. When a condition is required, it can be executed downward. (You can determine whether a condition object exists, to determine whether the conditions are met) use the await () method. At this time, the current thread is blocked and the lock is abandoned. After the execution of another thread is complete, call the signalAll () method when the condition is met to activate all threads waiting for one condition.
Lock lock = new ReentrantLock (); Condition condition = lock. newCondition (); lock. lock (); try {if (// determines whether a condition is met, if not) conditon. await (); // code block in the critical section. This ensures that only one thread enters the critical section at any time. Once a thread blocks the lock object, no other thread can use the lock statement // when the lock is called, they are blocked until the first thread releases the lock object... // after the execution of other threads, the current condition may be met and the condition may be re-activated. signalAll ();} finally {lock. unlock ();}
Use the synchronized keyword
Declare the method with the synchronized keyword. From jdk1.0, java provides an internal lock for each object. If a method is modified with the synchronized keyword, the object lock protects the entire method, that is, the method is called, the thread must obtain the internal Lock Object. The internal Lock Object also has a related condition. The wait () method adds a thread to the waiting set, and the Y/notofyAll method contacts the blocking status of the waiting thread.
That is, the wait () method is equivalent to the above await () method, and the policy/policyall method is equivalent to the above signal/signalAll method.
Public synchronized void method () {if (// condition status) wait ();... policyall ();}
In this way, locking the method is required for the current method, and synchronization blocking is also required.
Synchronized (synchronization Monitor-Lock Object) {// code block}
Select the appropriate Lock Object:
Synchronized needs to lock an object to ensure thread synchronization, so this object should be guaranteed. When multiple threads to be synchronized access this synchronization block, they should see the same lock object, otherwise, the synchronization effect cannot be reached. this is usually used as the Lock Object.
So when will the display thread lock/unlock be used and the synchronized keyword be used?
In the best case, it is not used. A mechanism in the java. util. concurrnt package locks the code you are processing. For example, you can use a blocking queue to complete
If you use the synchronized keyword for your program, try to use it to reduce the amount of code and prevent errors.
ExecutorService implementation Thread Pool
Thread Pool function: control the number of threads and reuse threads
If a large number of threads are created in a program and destroyed after the task is completed, excessive resource consumption and the risk of thread switching may occur to the system, leading to system crash, you can use the thread pool to solve this problem.
Principle: first, create some threads. Their collections become thread pools. When the server receives a request from the client, it extracts an idle thread from the thread pool, after the service is completed, the thread is returned to the thread pool instead of being closed. In the thread pool programming model, a task is submitted to the thread pool, not to a thread. After the thread pool obtains the task, it finds whether there are Idle threads in the thread pool, submit the task to a idle thread. One thread can only execute one task, but multiple tasks can be submitted to the thread pool at the same time.
Implementation Method:
1,
ExecutorService pool = Executors.newCachedThreadPool();
Create a thread pool where new threads can be created as needed, but they will be reused when previously constructed threads are available.
2,
ExecutorService pool = Executors.newFixedThreadPool(nThreads);
Create a thread pool of a fixed set and run these threads by sharing the unbounded queue mode.
3,
ExecutorService pool = Executors.newScheduledThreadPool(corePoolSize)
Create a thread pool to run commands or execute them regularly after a given delay.
4,
ExecutorService pool = Executors.newSingleThreadExecutor();
Create an Executor that uses a single work thread and run the thread in an unbounded sequence.
Class SampleThread {public static void main (String [] args) {ExecutorService pool = Executors. newFixedThreadPool (2); Handler handler = new Handler (); for (int I = 0; I <5? I ==##pool.exe cute (handler );}}} class Handler implements Runnable {@ Overridepublic void run () {String name = Thread. currentThread (). getName (); for (int I = 0; I <10; I ++) {System. out. println (the current Thread is: + name); try {Thread. sleep (1000);} catch (InterruptedException e) {e. printStackTrace () ;}} System. out. println (task execution completed );}}
BlockingQueue
BlockingQueue is a dual-buffer sequence. You can use Query if you need to use a queue for multi-thread concurrency. However, the synchronization operation reduces the number of concurrent Query operations. BlockingQuery can be used.
BlockingQueue uses two internal queues, which allow two threads to store and read data to the queue at the same time. This improves the queue access efficiency while ensuring concurrency security.
When BlockingQueue is empty, the thread is interrupted and waits until BlockingQueue has something to do. If the BlockingQueue queue is full, the thread will be interrupted and enters the waiting state when it is loaded into the queue until there is space in BlockingQueue.
| |
Throw an exception |
Special Value |
Blocking |
Timeout |
| Insert |
Add (e) |
Offer (e) |
Put (e) |
Offer (e, time, unit) |
| Remove |
Remove () |
Poll () |
Take () |
Poll (time, unit) |
| Check |
Element () |
Peek () |
Unavailable |
Unavailable |
Add (e): add an element to the queue. If BlockingQueue can be accommodated, true is returned; otherwise, an exception is thrown.
Offer (e): adds an element to the queue. If BlockingQueue can be accommodated, true is returned; otherwise, false is returned.
Put (e): adds an element to the queue. If BlockingQueue can accommodate it, It is inserted. If it cannot be inserted, the thread is blocked until it can be inserted.
The following method also works.
Four implementation classes of BlockingQueue
ArrayBlockingQueue: Specifies the BlockingQueue of the specified size. An int type parameter must be specified to specify its size. The objects contained in the parameter are sorted in FIFO order.
LinkedBlockingQueue: The BlockingQueue of an indefinite size. If the constructor specifies a size, it is the BlockingQueue queue size. If not specified, it is the size of Integer. MAX_VALUE, and the objects contained are sorted in FIFO order.
PriorityBlockingQueue: similar to LinkedBlockQueue, but not FIFO ordered. The order is determined based on the natural order of the object or the Comparator specified by the constructor.
SynchronousBlockingQueue: It is a special BlockingQueue that needs to be executed alternately.
Compared with ArrayBlockingQueue, the data structures behind them are different. As a result, the data throughput of LinkedBlockingQueue is larger than that of ArrayBlockingQueue. However, when the number of threads is large, the performance of the queue is lower than that of ArrayBlockingQueue.
Check the source code and you will find that the ReentrantLock object is added to each operation in the queue.
Test the offer method. If the sequence is not inserted for more than two seconds, false is returned.
BlockingQueue
Queue = new ArrayBlockingQueue
(10); for (int I = 0; I <20; I ++) {try {boolean result = queue. offer (I, 2, TimeUnit. SECONDS); // returns falseSystem if the data fails to be inserted for 2 SECONDS. out. println (whether the storage is successful: + result);} catch (InterruptedException e) {e. printStackTrace ();}}
Test the poll () method. If no element is retrieved in more than 2 seconds, false is returned.
BlockingQueue
Queue = new ArrayBlockingQueue
(10); for (int I = 0; I <10; I ++) {queue. offer (I) ;}for (int I = 0; I <20; I ++) {try {Integer result = queue. poll (2, TimeUnit. SECONDS); // if no element is obtained after two SECONDS, nullSystem is returned. out. println (result: + result);} catch (InterruptedException e) {e. printStackTrace ();}}