【JAVA Thread】-Outline

1. Thread state transition

New

  • It has not been started after creation.

Runnable

  • It may be running, or it may be waiting for a CPU time slice.
    Contains Running and Ready in the operating system thread status.

Blocked

  • Waiting to acquire an exclusive lock, if the thread releases the lock, it will end this state.

Waiting indefinitely (Waiting)

  • Wait for other threads to wake up explicitly, otherwise no CPU time slice will be allocated.

Timed Waiting

  • There is no need to wait for other threads to wake up explicitly, and will be automatically awakened by the system after a certain period of time.
    When calling the Thread.sleep() method to make a thread enter the waiting state for a limited time, it is often described as "putting a thread to sleep".
    When calling the Object.wait() method to make a thread wait for a limited time or wait indefinitely, it is often described as "suspending a thread".
    Sleep and suspend are used to describe behavior, while blocking and waiting are used to describe state.
    The difference between blocking and waiting is that blocking is passive, it is waiting to acquire an exclusive lock. While waiting is active, it is entered by calling methods such as Thread.sleep() and Object.wait().

Death (Terminated)

  • It can be that the thread ends after the task ends, or it ends with an exception.

Second, use threads

Implement the Runnable interface

  • Need to implement the run() method.
    Start the thread by calling the start() method of Thread.

Implement the Callable interface

  • Compared with Runnable, Callable can have a return value, and the return value is encapsulated by FutureTask.

Inherit the Thread class

  • It is also necessary to implement the run() method, because the Thread class also implements the Runable interface.
    When the start() method is called to start a thread, the virtual machine puts the thread in the ready queue and waits to be scheduled. When a thread is scheduled, the run() method of the thread is executed.

Implement interface VS inherit Thread

  • It is better to implement the interface because:

Java does not support multiple inheritance, so if you inherit the Thread class, you cannot inherit other classes, but you can implement multiple interfaces; the
class may only be executable, and inheriting the entire Thread class is too expensive.

Three, the basic thread mechanism

Executor

  • Executor manages the execution of multiple asynchronous tasks without the programmer needing to explicitly manage the life cycle of threads. Asynchronous here means that the execution of multiple tasks does not interfere with each other and does not need to be synchronized.
  • There are three main types of Executor:

CachedThreadPool: one task creates one thread;
FixedThreadPool: all tasks can only use fixed-size threads;
SingleThreadExecutor: a thread pool with only one thread.

Daemon

  • The daemon thread is a thread that provides services in the background while the program is running, and is not an indispensable part of the program.
    When all non-daemon threads end, the program is terminated, and all daemon threads are killed at the same time.
    Before the thread starts, use the setDaemon() method to set a thread as a daemon thread.

sleep()

  • The Thread.sleep(millisec) method sleeps the currently executing thread, and the unit of millisec is milliseconds.

sleep() may throw InterruptedException because the exception cannot be propagated back to main() across threads, so it must be handled locally. Other exceptions thrown in the thread also need to be handled locally.

yield()

  • The call to the static method Thread.yield() declares that the current thread has completed the most important part of the life cycle and can be switched to other threads for execution. This method is only a suggestion to the thread scheduler, and it only suggests that other threads with the same priority can run.

Four, interrupt

InterruptedException

  • By calling the interrupt() of a thread to interrupt the thread, if the thread is blocked, waiting for a limited time or waiting indefinitely, then an InterruptedException will be thrown to terminate the thread early. However, I/O blocking and synchronized lock blocking cannot be interrupted.

Executor's interrupt operation

  • Calling the shutdown() method of Executor will wait for the threads to execute before shutting down, but if the shutdownNow() method is called, it is equivalent to calling the interrupt() method of each thread.
  • If you only want to interrupt a thread in the Executor, you can submit a thread by using the submit() method, it will return a Future<?> object, and you can interrupt the thread by calling the cancel(true) method of the object.

Five, mutually exclusive synchronization

synchronized

    1. Synchronize a code block
    1. Synchronize a (static) method
    1. Synchronize a class

ReentrantLock

  • ReentrantLock is a lock in the java.util.concurrent (JUC) package.

Compare

    1. Implementation of the lock
    • Synchronized is implemented by JVM, and ReentrantLock is implemented by JDK.
    1. performance
    • The new version of Java has made many optimizations to synchronized, such as spin locks, etc. Synchronized and ReentrantLock are roughly the same.
    1. Waiting to be interrupted
    • When the thread holding the lock does not release the lock for a long time, the waiting thread can choose to give up waiting and deal with other things instead.

ReentrantLock can be interrupted, but synchronized cannot.

    1. Fair lock
    • A fair lock means that when multiple threads are waiting for the same lock, they must acquire the locks in sequence in the order in which they apply for the lock.

The lock in synchronized is unfair, ReentrantLock is also unfair by default, but it can also be fair.

    1. Lock multiple conditions
    • A ReentrantLock can bind multiple Condition objects at the same time.

Use selection

  • Unless you need to use the advanced features of ReentrantLock, synchronize is preferred. This is because synchronized is a lock mechanism implemented by JVM, which is natively supported by JVM, and ReentrantLock is not supported by all JDK versions. And when using synchronized, you don't have to worry about deadlock problems caused by not releasing the lock, because the JVM will ensure the release of the lock.

Six, collaboration between threads

When multiple threads can work together to solve a problem, if some parts must be completed before other parts, then the threads need to be coordinated.

join()

  • Calling the join() method of another thread in a thread will suspend the current thread instead of busy waiting until the target thread ends.

wait() notify() notifyAll()

  • Calling wait() makes the thread wait for a certain condition to be satisfied, and the thread will be suspended while waiting. When the running of other threads makes this condition satisfied, other threads will call notify() or notifyAll() to wake up the suspended thread.

They are all part of Object, not Thread.

It can only be used in synchronous methods or synchronous control blocks, otherwise an IllegalMonitorStateException will be thrown at runtime.

During the suspension using wait(), the thread releases the lock. This is because if the lock is not released, other threads cannot enter the synchronization method or synchronization control block of the object, and then the notify() or notifyAll() cannot be executed to wake up the suspended thread, causing a deadlock.

await() signal() signalAll()

  • The Condition class is provided in the java.util.concurrent class library to realize the coordination between threads. You can call the await() method on the Condition to make the thread wait, and other threads call the signal() or signalAll() method to wake up the waiting thread.

Compared with wait() this way of waiting, await() can specify the waiting conditions, so it is more flexible.

Use Lock to obtain a Condition object.

七、J.U.C - AQS

AQS

  • java.util.concurrent (JUC) greatly improves concurrency performance, and AQS is considered to be the core of JUC.

CountDownLatch

  • Used to control one or more threads to wait for multiple threads.

A counter cnt is maintained. Each time the countDown() method is called, the value of the counter is reduced by 1, and when it reaches 0, the threads that are waiting because of the call to the await() method will be awakened.

CyclicBarrier

  • Used to control multiple threads to wait for each other, only when multiple threads arrive, these threads will continue to execute.

Semaphore

  • Semaphore is similar to the semaphore in the operating system and can control the number of threads that access mutually exclusive resources.

8. JUC-other components

FutureTask

  • FutureTask can be used to asynchronously obtain the execution result or cancel the execution of the task.
  • FutureTask implements the RunnableFuture interface, which inherits from the Runnable and Future interfaces, which enables FutureTask to be executed as a task or have a return value.

BlockingQueue

  • The java.util.concurrent.BlockingQueue interface has the following implementations of blocking queues:

FIFO queue: LinkedBlockingQueue, ArrayBlockingQueue (fixed length)
priority queue: PriorityBlockingQueue

Blocking take() and put() methods are provided: if the queue is empty, take() will block until there is content in the queue; if the queue is full, put() will block until there is a free place in the queue.

ForkJoin

  • Mainly used in parallel computing, similar to the principle of MapReduce, which splits a large computing task into multiple small tasks for parallel computing.
  • ForkJoinPool implements a work stealing algorithm to improve CPU utilization. Each thread maintains a deque to store tasks that need to be executed. The work stealing algorithm allows idle threads to steal a task from the deque of other threads for execution. The stolen task must be the latest task to avoid competition with the thread to which the queue belongs.

Nine, thread unsafe example

If multiple threads access the same shared data without taking synchronization operations, the results of the operations are inconsistent.

10. Java memory model

Main memory and working memory

  • The read and write speed of the registers on the processor is several orders of magnitude faster than that of the memory. In order to solve this speed contradiction, a cache is added between them.

Joining the cache brings a new problem: cache coherency. If multiple caches share the same main memory area, the data of multiple caches may be inconsistent, and some agreement is needed to solve this problem.

All variables are stored in the main memory, and each thread has its own working memory. The working memory is stored in a cache or a register, and a copy of the main memory of the variables used by the thread is saved.

Threads can only directly manipulate variables in the working memory, and the variable value transfer between different threads needs to be completed through the main memory.

Inter-memory operation

  • The Java memory model defines 8 operations to complete the interactive operation of main memory and working memory.

read: transfer the value of a variable from the main memory to the working memory
load: execute after read, put the value obtained by read into the variable copy of
the working memory use: pass the value of a variable in the working memory to the execution engine
assign : Assign a value received from the execution engine to the variable of
the working memory store: transfer the value of a variable of the working memory to the main memory
write: execute after the store, put the value obtained by the store into the variable of the main memory
lock: variable
unlock that acts on the main memory

Three characteristics of the memory model

    1. Atomicity
    • The Java memory model guarantees that read, load, use, assign, store, write, lock and unlock operations are atomic. For example, perform an assign assignment to an int type variable. This operation is atomic. However, the Java memory model allows the virtual machine to divide the read and write operations of 64-bit data (long, double) that are not modified by volatile into two 32-bit operations, that is, load, store, read, and write operations may not be atomic .
    1. Visibility
    • Visibility means that when a thread modifies the value of a shared variable, other threads can immediately learn about the modification. The Java memory model achieves visibility by synchronizing the new value back to the main memory after the variable is modified, and refreshing the variable value from the main memory before the variable is read.
    • There are three main ways to achieve visibility:
      volatile
      synchronized, before performing an unlock operation on a variable, the value of the variable must be synchronized back to the main memory.
      final
    1. Orderliness
    • Orderliness refers to: Observing in this thread, all operations are ordered. When one thread observes another thread, all operations are out of order. The out of order is due to instruction reordering.
      In the Java memory model, the compiler and processor are allowed to reorder instructions. The reordering process will not affect the execution of a single-threaded program, but it will affect the correctness of concurrent execution of multiple threads.
    • The volatile keyword prohibits instruction rearrangement by adding a memory barrier, that is, the following instructions cannot be placed before the memory barrier during reordering.

Ordering can also be ensured by synchronized, which ensures that only one thread executes the synchronization code at a time, which is equivalent to let the threads execute the synchronization code sequentially.

First occurrence principle

    1. Single thread principle
    • In a thread, the operations in the front of the program occur before the operations in the back.
    1. Tube Locking Rules
    • An unlock operation occurs first in subsequent lock operations on the same lock.
    1. Volatile variable rules
    • A write operation to a volatile variable occurs first in a subsequent read operation to this variable.
    1. Thread start rule
    • The start() method call of the Thread object precedes every action that occurs in this thread.
    1. Thread joining rules
    • The end of the Thread object occurs first when the join() method returns.
    1. Thread interruption rule
    • The call to the thread interrupt() method occurs first when the code of the interrupted thread detects the occurrence of an interrupt event, and the interrupted() method can be used to detect whether an interrupt has occurred.
    1. Object termination rules
    • The completion of the initialization of an object (the end of the execution of the constructor) occurs first at the beginning of its finalize() method.
    1. Transitivity
    • If operation A occurs before operation B, and operation B occurs before operation C, then operation A occurs before operation C.

Eleven, thread safety

concept

  • Multiple threads can behave correctly no matter how they access a certain class and do not need to be synchronized in the main calling code.

Immutable

  • Immutable objects must be thread-safe, and there is no need to take any thread-safety measures. As long as an immutable object is constructed correctly, it will never be seen in an inconsistent state among multiple threads. In a multithreaded environment, objects should be made as immutable as possible to satisfy thread safety.

Mutually exclusive synchronization

  • synchronized 和 ReentrantLock。

Mutual exclusion synchronization is a pessimistic concurrency strategy, always thinking that as long as the correct synchronization measures are not taken, there will definitely be problems. Regardless of whether there is competition for shared data, it must be locked (the conceptual model discussed here, in fact, the virtual machine optimizes a large part of unnecessary locking), user-mode core state conversion, maintenance of lock counters and Check whether there are blocked threads that need to be awakened and other operations.

Non-blocking synchronization

  • With the development of the hardware instruction set, we can use an optimistic concurrency strategy based on conflict detection: operate first, if no other threads compete for shared data, then the operation is successful, otherwise take compensation measures (continuously retry until success until). Many implementations of this optimistic concurrency strategy do not need to block threads, so this synchronization operation is called non-blocking synchronization.

    1. CASE
    • Optimistic locking requires atomicity in the two steps of operation and conflict detection. Mutex synchronization can no longer be used to ensure that it can only be done by hardware. The most typical hardware-supported atomic operation is: Compare-and-Swap (CAS).
      The CAS instruction requires 3 operands, which are the memory address V, the old expected value A, and the new value B. When performing an operation, only when the value of V is equal to A, will the value of V be updated to B.
    1. AtomicInteger
    • The method of the integer atomic class AtomicInteger in the JUC package calls the CAS operation of the Unsafe class.
    1. ABA
    • If a variable is the value of A when it is first read, its value is changed to B, and then changed back to A, the CAS operation will mistakenly believe that it has never been changed.

The JUC package provides a tagged atomic reference class AtomicStampedReference to solve this problem. It can ensure the correctness of CAS by controlling the version of the variable value.

No synchronization scheme

    1. Stack closed
    • When multiple threads access the local variables of the same method, there will be no thread safety issues, because the local variables are stored in the virtual machine stack and are private to the thread.
    1. Thread local storage (ThreadLocal)
    • If the data required in a piece of code must be shared with other codes, then see if the code that shares data can be guaranteed to be executed in the same thread. If it can be guaranteed, we can limit the visible range of shared data to the same thread, so that there is no need for synchronization to ensure that there is no data contention between threads.
    1. Reentrant Code
    • This kind of code is also called pure code. It can be interrupted at any time during the execution of the code, and then another piece of code (including recursively calling itself) can be executed, and the original program will not appear after the control is returned. Any errors.

12. Lock optimization

Spin lock

  • The idea of ​​spin lock is to allow a thread to execute a busy loop (spin) for a period of time when requesting a lock for shared data. If the lock can be acquired during this period, it can avoid entering a blocking state.

Adaptive spin lock

  • In JDK 1.6, an adaptive spin lock was introduced. Adaptive means that the number of spins is no longer fixed, but is determined by the number of spins on the same lock the previous time and the state of the lock owner.

Lock elimination

  • Lock elimination refers to the elimination of locks on shared data that are detected to be unlikely to have contention.
    Lock elimination is mainly supported by escape analysis. If shared data on the heap cannot escape and be accessed by other threads, then they can be treated as private data, and their locks can be eliminated.

Chain coarsening

  • If a series of continuous operations repeatedly lock and unlock the same object, frequent locking operations will cause performance loss.

Lightweight lock

  • Lightweight locks are relative to traditional heavyweight locks. They use CAS operations to avoid the overhead of heavyweight locks using mutex.
    When trying to acquire a lock object, if the lock of the lock object is unlocked. At this time, the virtual machine creates a Lock Record in the virtual machine stack of the current thread, and then uses the CAS operation to update the Mark Word of the object to the Lock Record pointer. If the CAS operation is successful, the thread acquires the lock on the object, and the lock mark of the Mark Word of the object becomes a lightweight lock state.
    If the CAS operation fails, the virtual machine will first check whether the Mark Word of the object points to the virtual machine stack of the current thread. If it is, it means that the current thread already owns the lock object, then it can directly enter the synchronization block to continue execution, otherwise it means this The lock object has been preempted by another thread. If there are more than two threads competing for the same lock, the lightweight lock is no longer valid and will expand to a heavyweight lock.

Bias lock

  • The idea of ​​biased locks is to favor the first thread that acquires the lock object. After this thread acquires the lock, it no longer needs to perform synchronization operations, and even CAS operations are no longer needed.
    When another thread tries to acquire the lock object, the biased state is declared to be over. At this time, after revoke bias (Revoke Bias), the state is restored to the unlocked state or the lightweight lock state.

Thirteen, good practice of multi-threaded development

Give the thread a meaningful name so that you can find bugs easily.

Reduce the scope of synchronization, thereby reducing lock contention. For example, for synchronized, you should try to use synchronized blocks instead of synchronized methods.

Use more synchronization tools less wait() and notify(). First of all, synchronization classes such as CountDownLatch, CyclicBarrier, Semaphore and Exchanger simplifies coding operations, but it is difficult to achieve complex control flow with wait() and notify(); secondly, these synchronization classes are written and maintained by the best companies. The JDK will continue to be optimized and improved.

Use BlockingQueue to implement producer-consumer problems.

Use concurrent collections and use less synchronized collections. For example, ConcurrentHashMap should be used instead of Hashtable.

Use local variables and immutable classes to ensure thread safety.

Use thread pool instead of directly creating threads, this is because the cost of creating threads is high, and thread pools can effectively use limited threads to start tasks.

Guess you like

Origin blog.csdn.net/dong8633950/article/details/114448412