1. Basic knowledge

Thread start

The only way to start a thread is:

1, ThreadXX extends Thread; And then ThreadXX. StartCopy the code
2; XRunble implements Runnable; It is then handed over to Thread to runCopy the code

Thread state

There are six states of threads in Java:

  1. Initial (NEW) : A NEW thread object is created, but the start() method has not yet been called.
  2. RUNNABLE: Java threads refer to the ready and running states as “RUNNABLE.” After the thread object is created, other threads, such as the main thread, call the start() method of the object. A thread in this state is in the runnable thread pool, waiting to be selected by thread scheduler for CPU usage, and is in the ready state. A thread in the ready state becomes running after obtaining a CPU time slice.
  3. BLOCKED: A thread is BLOCKED from a lock.
  4. WAITING: a thread in this state is WAITING for some specific action (notification or interrupt) from another thread.
  5. TIMED_WAITING: This state is different from WAITING. It can return after a specified time.
  6. TERMINATED: indicates that the thread is TERMINATED.

Transitions between states are shown below

A deadlock

concept

A blockage in the execution of two or more processes, caused by competing for resources or by communicating with each other, which will not proceed without external forces. The system is in a deadlock state or a deadlock occurs.

For example: A and B go to massage and wash their feet. They both want to do A head massage while washing their feet. 13 technicians are good at foot massage, 14 are good at head massage.

At this time, A grabs 14 first, B grabs 13 first, and they both want to wash their feet and massage their heads at the same time, so they won’t give way to each other, threatening that I will not let you die. In this case, A grabs 14, wants 13, B grabs 13, wants 14, and A and B have A deadlock in the matter of wanting to wash their feet and massage their heads at the same time. How to solve this problem?

First, if at this time, A 15, who happens to be good at head massage, and A does not have two heads, it will naturally go to B, so B will be delighted to wash feet and do head massage, leaving A beside the air, at this time the deadlock situation will be broken, does not exist.

In the second case, C comes out and forces A and B to wash their feet first and then massage their heads. In this case, the one who grabs 13 first can proceed, and the other one who doesn’t grab 13 can wait. In this case, deadlock will not occur.

So to sum up:

Deadlock occurs when multiple operators (M>=2) compete for multiple resources (N>=2, and N<=M). If you have a single thread, there will be no deadlocks. If you have a single thread, there will be no deadlocks. What about single resources? With only 13, A and B will have A fierce competition, and whoever gets it will be the one who gets it, but there will be no deadlock. Also, deadlocks have several requirements:

  1. If resources are contested in the wrong order, deadlock will not occur if resources are contested in the same order.
  2. The contender holds on to the resources.

Academic definition

There are four necessary conditions for a deadlock to occur.

  • 1) Mutually exclusive conditions: a process uses the allocated resources in an exclusive manner, that is, only one process occupies a certain resource in a period of time. If another process requests the resource at this time, the requester can only wait until the process holding the resource is released.
  • 2) Request and hold conditions: the process has held at least one resource, but it puts forward a new resource request, and the resource has been occupied by other processes. In this case, the requesting process blocks, but does not release other resources it has obtained.
  • 3) Non-deprivation condition: the process can not be deprived of the resources it has acquired before they are used up, but can only release them when they are used up.
  • 4) Loop waiting condition: when deadlock occurs, there must be a process — resource loop chain, that is, P0 in process set {P0, P1, P2, ··· Pn} is waiting for a resource occupied by P1; P1 is waiting for resources occupied by P2…… Pn is waiting for a resource that has been occupied by P0.

Understanding the causes of deadlocks, especially the four necessary conditions for deadlocks, can best avoid, prevent, and remove deadlocks.

Deadlock can be effectively prevented by breaking one of the four necessary conditions.

  • Break mutually exclusive conditions: Change an exclusive resource to a virtual resource. Most resources cannot be changed.
  • Breaking the non-preemption condition: when a process occupies an exclusive resource and then applies for an exclusive resource that cannot be satisfied, it exits the originally occupied resource.
  • Break the occupation and application conditions: adopt the resource pre-allocation strategy, that is, apply for all resources before the process runs, run if the process meets the requirements, or wait, so that the process will not be occupied and application.
  • Breaking the waiting conditions: Implements the resource allocation policy in an orderly manner. All devices are numbered by category. All processes can apply for resources only in ascending order.

Common algorithms to avoid deadlocks include orderly resource allocation and banker algorithm.

harm

  1. The thread stops working, but the entire program is still alive

  2. There’s nothing out of the ordinary for us to check.

  3. Once a deadlock occurs in an application, there is no way to recover, but to restart the application, which is a serious problem for officially released applications.

To solve

The key is to make sure you pick the locks in the same order

Two solutions

  1. Internal through the sequence comparison, to determine the order of the lock;
  2. Use the mechanism of trying to get the lock.

Other thread safety issues

Live lock

In the mechanism of two threads trying to get a lock, there is mutual compromise between multiple threads. The same thread always gets the same lock, but when trying to get another lock, it will release the lock that it has already held.

** Each thread sleeps the random number and staggers the lock picking time.

Thread starvation

Low priority threads always get no execution time

ThreadLocal differentiate

Compared with Synchonized

ThreadLocal and Synchonized are both used to deal with multi-threaded concurrent access. But ThreadLocal is fundamentally different from synchronized. Synchronized is a mechanism that uses locks to make variables or code blocks accessible to only one thread at a time. ThreadLocal, on the other hand, provides a copy of a variable for each thread, so that each thread is not accessing the same object at any one time, thus isolating multiple threads from sharing data.

The use of ThreadLocal

The ThreadLocal class interface is simple, with only four methods, so let’s look at it first:

  • Sets the value of a thread-local variable for the current thread.
void set(Object value) 
Copy the code
  • This method returns the thread-local variable corresponding to the current thread.
 public Object get(a) 
Copy the code
  • Delete the value of the current thread-local variable in order to reduce memory usage. This method is new in JDK 5.0. It is important to note that local variables to the thread are automatically garbage collected when the thread terminates, so it is not necessary to explicitly call this method to clean up local variables of the thread, but it can speed up memory collection.
 public void remove(a) 
Copy the code
  • Returns the initial value of the thread-local variable, which is a protected method, obviously designed to be overridden by subclasses. This method is a deferred call that is executed only once, the first time the thread calls GET () or set(Object). The default implementation in ThreadLocal simply returns null.
protected Object initialValue(a) 
Copy the code
public final static ThreadLocal<String> RESOURCE = new ThreadLocal<String>();
Copy the code

RESOURCE represents a ThreadLocal object that can hold strings. No matter what thread can concurrently access the variable, write or read the variable, it is thread safe.

For the

ThreadLocalMap is a static inner class of ThreadLocal. ThreadLocalMap is a static inner class of ThreadLocal. ThreadLocalMap is a static inner class of ThreadLocal.

ThreadLocalMap (ThreadLocalMap);

An Entry static class inherits WeakReference. In short, it records two pieces of information. One is ThreadLocal<? > type, one is a value of type Object. The getEntry method retrieves the value of a ThreadLocal, and the set method updates or assigns the value of the corresponding ThreadLocal.

Looking back at our get method, we actually get a ThreadLocalMap unique to each thread

It then uses the current instance of ThreadLocal to get the corresponding Entry in the Map, which can then be returned with the corresponding value. Of course, if the Map is empty, the Map is created and initialized first.

2.CAS basic principle

Atomic operation

What is atomic operation?

Suppose that there are two operations A and B(both of which can be complex), and that A and B are atomic to each other if, from the point of view of the thread executing A, the other thread executing B either completes B entirely or does not perform B at all.

How are atomic operations implemented?

Atomic operations can be implemented using locks, locking mechanisms, to meet the basic needs of the problem, but sometimes our needs are not so simple, we need a more efficient and flexible mechanism, synchronized keyword is a blocking based locking mechanism, that is, when a thread has a lock, Other threads accessing the same resource need to wait until the thread releases the lock.

Why CAS

There are some problems: First, what if the blocked thread is of high priority and important? Second, what if the thread that acquired the lock never released it? (This situation is very bad). There is a kind of situation, if there is a large number of threads to compete for resources, the CPU will spend a lot of time and resources to deal with the competition, at the same time, there are some possible deadlock situations such as, for example, in the end, the locking mechanism is actually a relatively coarse, particle size bigger of the two mechanisms, such as counter relative to demand a little too heavy.

Atomic operations can also be implemented using the CAS() instruction, which is supported by most current processors. However, the algorithm implemented by each vendor is different. Each CAS operation contains three operators: A memory address V, A desired value A, and A new value b. If the value stored at the address is equal to the desired value A, the value of the address is assigned to the new value b. Otherwise, no operation is performed.

The basic idea of CAS is that if the value at this address is the same as the expected value, it assigns it a new value, otherwise it does nothing, but returns what the original value was. Cyclic CAS means that the CAS operation is repeated in a loop until it succeeds.

Three major problems with CAS implementation of atomic operations

ABA problem

Because CAS needs to check if the value has changed and update it if it hasn’t, if A value that was originally A changes to B and then to A, then CAS checks to find that the value hasn’t changed, but in fact it has.

The solution

The solution to the ABA problem is to use version numbers. Add the version number to the variable, and each time the variable is updated, add the version number by 1. Then A→B→A will change to 1A→2B→3A. For example, if you put a glass of water on the table and do something else, then your colleague drinks your water and pours you a new glass of water. When you come back and see that the water is still there, you pick it up and drink it. If you don’t care that the water has been drunk, you only care that the water is still there.

If you are a man who speaks sanitation and civilization, you not only care about whether the water is in, but also whether the water has been touched when you leave. Because you are a programmer, you remember to put a piece of paper next to it and write down the initial value 0.

Long cycle time and high overhead

Spin CAS, if unsuccessful for a long period of time, can impose a significant execution overhead on the CPU.

Atomic operations of only one shared variable are guaranteed

When performing operations on a shared variable, we can loop CAS to ensure atomic operations, but when performing operations on multiple shared variables, the loop CAS cannot guarantee atomic operations, so we can use locks.

Another trick is to combine multiple shared variables into a single shared variable. For example, if you have two shared variables I =2, j=a, merge ij=2a, and then use CAS to manipulate ij. Since Java 1.5, the JDK has provided the AtomicReference class to ensure atomicity between reference objects, allowing multiple variables to be placed in a single object for CAS operations.

Use of related atomic manipulation classes in the JDK

AtomicInteger

  • Int addAndGet (int delta) : Atomically adds the input value to the value in the instance (the value in the AtomicInteger) and returns the result.
  • Boolean compareAndSet (int expect, int update) : If the value entered is equal to the expected value, set it to the input value atomically.
  • Int getAndIncrement() : Atomically increments the current value by 1.
  • Int getAndSet (int newValue) : The value set atomically to newValue and returns the old value.

AtomicIntegerArray

It mainly provides atomic way to update the integer in the array, its common methods are as follows.

  • Int addAndGet (int I, int delta) : Atomically adds the input value to the elements of the array index I.
  • Boolean compareAndSet (int I, int expect, int UPDATE) : Atomically set the element at position I to update if the current value is equal to the expected value.

Note that the array value is passed in through the constructor, and the AtomicIntegerArray makes a copy of the current array, so when the AtomicIntegerArray makes changes to the internal array elements, it doesn’t affect the array passed in.

Update reference type

Atomic update AtomicInteger of the base type, which can update only one variable, is used to update the class provided by the reference type if multiple variables are to be updated atomically. The Atomic package provides the following three classes.

AtomicReference

Atomic update reference types.

AtomicStampedReference

The version number after each change is recorded in the form of a version stamp, so that there is no ABA problem. This is the solution for AtomicStampedReference. AtomicMarkableReference is similar to an AtomicStampedReference, which uses the int stamp of a pair as a counter. AtomicMarkableReference pairs use Boolean mark. So again, your AtomicStampedReference is probably about how many times it’s been touched, and your AtomicMarkableReference is about whether it’s been touched, so it’s pretty simple.

AtomicMarkableReference

Atom updates reference types with tagged bits. You can atomically update a Boolean tag bit and reference type. The constructor is AtomicMarkableReference (V initialRef, booleaninitialMark).

3. Block the queue

Blocking queue

The queue

A queue is a special kind of linear table, special in that it only allows deletion at the front of the table and insertion at the rear. Like a stack, a queue is a linear table with limited operation. The end that inserts is called the end of the queue, and the end that deletes is called the head of the queue.

Inserting a queue element into a queue is called enqueueing, and removing a queue element from a queue is called enqueueing. Because the queue can only be inserted at one end and deleted at the other end, only the earliest element can be deleted from the queue first, so the queue is also called FIFO – first in first out (FIFO – first in first out) linear table.

What is a blocking queue

  1. Blocking insertion method: this means that when the queue is full, the queue will block the thread that inserted the element until the queue is full.
  2. Blocking removal is supported: this means that when the queue is empty, the thread that fetched the element will wait for the queue to become non-empty.

Using the producer and consumer pattern in concurrent programming solves most concurrency problems. This mode improves the data processing speed of the whole program by balancing the working capacity of the production thread and the consumption thread.

In the threaded world, producers are the threads that produce the data, and consumers are the threads that consume the data. In multithreaded development, if the producer is fast and the consumer is slow, the producer must wait for the consumer to finish processing before continuing to produce data. In the same way, if the processing power of the consumer is greater than that of the producer, then the consumer must wait for the producer.

In order to solve the problem of unbalanced production and consumption power, there is the producer and consumer model. The producer-consumer pattern solves the strong coupling problem of producer and consumer through a container. No direct communication between producers and consumers to each other, but by blocking queue to communicate, so after producers to produce the data need not wait for the consumer process, directly to the blocking queue, consumers are looking for producers to data, but directly from the blocking queue, blocking queue is equivalent to a buffer, balance the processing capacity of producers and consumers.

Blocking queues are often used in producer and consumer scenarios, where the producer is the thread that adds elements to the queue and the consumer is the thread that fetches elements from the queue. Blocking queues are containers that producers use to store elements and consumers use to get them.

  • Throw exception: When the queue is full, IllegalStateException (“Queuefull”) is thrown if additional elements are inserted into the queue. When the queue is empty, fetching elements from the queue throws a NoSuchElementException.
  • Return special value: When an element is inserted into the queue, it returns whether the element was successfully inserted, and true on success. The remove method retrieves an element from the queue, or returns NULL if it does not.
  • Keep blocking: When the blocking queue is full, if the producer thread puts elements to the queue, the queue will keep blocking the producer thread until the queue is available or the response interrupts and exits. When the queue is empty, if the consumer thread takes elements from the queue, the queue blocks the consumer thread until the queue is not empty.
  • Timeout exit: When the blocking queue is full, if the producer thread inserts elements into the queue, the queue blocks the producer thread for a certain period of time, and the producer thread exits if the specified period of time is exceeded.

Common blocking queue

  • ArrayBlockingQueue: A bounded blocking queue composed of array structures.
  • LinkedBlockingQueue: A bounded blocking queue consisting of a linked list structure.
  • PriorityBlockingQueue: An unbounded blocking queue that supports priority sorting.
  • DelayQueue: an unbounded blocking queue implemented using a priority queue.
  • SynchronousQueue: A blocking queue that does not store elements.
  • LinkedTransferQueue: An unbounded blocking queue consisting of a linked list structure.
  • LinkedBlockingDeque: A bidirectional blocking queue consisting of a linked list structure.

All of the above blocking queues implement the BlockingQueue interface and are thread safe.

Bounded or unbounded?

Limited length of queue is limited, is full of producers will block, unbounded queue is inside can put a myriad of things, not because the queue length limit was blocked, space constraints, of course, comes from the limitation of system resource, if treatment is not promptly, causes the queue growing bigger and bigger, beyond a certain limit the memory overrun, operating system or the JVM to help you solve problems, Save yourself the OOM kill.

Unbounded blocks. Why? Because blocking doesn’t just happen when a producer puts an element in, it also happens when a consumer takes an element out if there’s no element in it.

ArrayBlockingQueue

Is a bounded blocking queue implemented with an array. This queue sorts elements on a first-in, first-out (FIFO) basis. A queue that does not guarantee fair access by default. The so-called fair access queue is a blocked thread that can access the queue in the order in which it is blocked, that is, the blocking thread accesses the queue first. Non-fairness is unfair to the threads that wait first. When the queue is available, the blocking threads can compete for access to the queue, and it is possible that the thread that blocks first will access the queue last. Parameters can be set during initialization

LinkedBlockingQueue

Is a bounded blocking queue implemented with linked lists. The default and maximum length of this queue is integer.max_value. This queue sorts elements on a first-in, first-out basis.

Differences between Array and Linked implementations

ArrayBlockingQueue LinkedBlockingQueue
Locks are implemented differently in queues There is no separation of locks, that is, the same lock is used for production and consumption Locks are detached, that is, putLocks are produced and takeLock is consumed
Operate differently during production or consumption Enumeration objects are inserted or removed directly during production and consumption; Enumeration objects need to be converted into Nodes for insertion or removal at production and consumption time, which can affect performance
The queue size is initialized differently The size of the queue must be specified Does not specify the size of the queue, but defaults to integer.max_value
PriorityBlockingQueue

PriorityBlockingQueue is an unbounded blocking queue that supports a priority. By default, elements are arranged in natural ascending order. You can also customize the class to implement the compareTo() method to specify the element collation rules, or when initializing PriorityBlockingQueue, specify the constructor parameter Comparator to sort the elements. Note that the order of elements of the same priority cannot be guaranteed.

DelayQueue

Is an unbounded blocking queue that supports delayed fetching of elements. The queue is implemented using PriorityQueue. The elements in the queue must implement the Delayed interface, which specifies when the element is created how long it will take to retrieve the current element from the queue. Elements can only be extracted from the queue when the delay expires.

DelayQueue is useful and can be used in the following scenarios.

** The design of the cache system: ** You can use a DelayQueue to store the expiration date of cached elements, and use a thread to loop through the DelayQueue. Once an element can be retrieved from the DelayQueue, the expiration date of the cache is up.

SynchronousQueue

Is a blocking queue that stores no elements. Each PUT operation must wait for a take operation or it cannot continue to add elements. SynchronousQueue can be thought of as a passer that passes data processed by the producer thread directly to the consumer thread. Queues themselves do not store any elements, making them ideal for transitive scenarios.

LinkedTransferQueue

With tryTransfer and transfer methods,

  • (1) Transfer method

    If there is a consumer currently waiting to receive an element (when the consumer uses a take() method or a poll() method with a time limit), the transfer method can immediately transfer an element passed in by a producer to the consumer. If no consumer is waiting to receive an element, the Transfer method stores the element at the tail node of the queue and does not return until the element is consumed by the consumer.

  • (2) tryTransfer method

    The tryTransfer method is used to test whether an element passed in by a producer can be passed directly to the consumer. Return false if there are no consumers waiting to receive elements. The difference between the tryTransfer method and the Transfer method is that the method will return immediately regardless of whether the consumer receives it or not, while the transfer method must wait until the consumer consumes before returning.

LinkedBlockingDeque

LinkedBlockingDeque is a two-way blocking queue consisting of a linked list structure. A two-way queue is one in which elements can be inserted and removed from both ends of the queue. The two-way queue has one more entry to the operation queue, reducing the contention in half when multiple threads join the queue at the same time.

There are addFirst, addLast, offerFirst, offerLast, peekFirst, peekLast, and other methods that end in the word First, which means insert, get (peek), or remove the First element of a two-ended queue. A method ending in the word Last that inserts, gets, or removes the Last element of a two-ended queue. In addition, the insert method add is equivalent to addLast, and the remove method remove is equivalent to removeFirst. However, the take method is the same as takeFirst, which is a BUG in the JDK. It is better to use First and Last suffixes. When initializing LinkedBlockingDeque, you can set the capacity to prevent it from swelling too much. In addition, two-way blocking queues can be used in “job stealing” mode.

4. The thread pool

Why thread pools?

Thread pools are the most widely used concurrency framework in Java, and can be used by almost any program that needs to perform tasks asynchronously or concurrently. There are three benefits to using thread pools properly during development.

  1. Reduce resource consumption. Reduce the cost of thread creation and destruction by reusing created threads.
  2. Improve response speed. When a task arrives, it can be executed immediately without waiting for the thread to be created. Suppose a server completes a task in terms of time T1 creates the thread, T2 executes the task in the thread, and T3 destroys the thread. If: T1 + T3 is much larger than T2, thread pools can be used to improve server performance. Thread pool technology focuses on shortening or adjusting T1 and T3 times to improve server application performance. It schedules T1 and T3 at the start and end of the server program, or at some idle time, so that the server program does not have the overhead of T1 and T3 when processing client requests.
  3. Improve thread manageability. Threads are scarce resources. If created without limit, they consume system resources and degrade system stability. Thread pools can be used for uniform allocation, tuning, and monitoring.

Class relationships of ThreadPoolExecutor

  • Executor: An interface that forms the basis of the Executor framework and separates the submission of tasks from their execution;
  • ExecutorService: The ExecutorService interface inherits from Executor, with extensions such as shutdown() and Submit (), and is a real thread pool interface.
  • AbstractExecutorService: Abstract class implements most of the ExecutorService interface methods;
  • ThreadPoolExecutor: the core implementation class of the thread pool used to execute the submitted task;
  • ScheduledExecutorService: The interface inherits the ExecutorService interface and provides the “periodic execution” function.
  • ScheduledThreadPoolExecutor: is an implementation class, can run in a given delay, or execute commands on a regular basis. ScheduledThreadPoolExecutor than the Timer is more flexible and more powerful.

The meanings of parameters for creating a thread pool

public ThreadPoolExecutor(intCorePoolSize, // Number of core threadsintMaximumPoolSize, // Maximum number of threadslongBlockingQueue<Runnable> workQueue, BlockingQueue<Runnable> workQueue RejectedExecutionHandler handler)  // Reject the policy
Copy the code

corePoolSize

The number of core threads in the thread pool that, when a task is submitted, creates a new thread to execute the task until the current number of threads equals corePoolSize;

If the current number of threads is corePoolSize, further submitted tasks are stored in a blocking queue, waiting to be executed.

If the thread pool’s prestartAllCoreThreads() method is executed, the thread pool creates and starts all core threads ahead of time.

maximumPoolSize

The maximum number of threads allowed in the thread pool. If the current blocking queue is full and the task continues to be submitted, a new thread is created to execute the task, provided that the number of current threads is less than maximumPoolSize

keepAliveTime

The lifetime of a thread when it is idle, that is, how long it continues to live when there are no tasks to execute. By default, this parameter is only useful if the number of threads is greater than corePoolSize

TimeUnit

KeepAliveTime Unit of keepAliveTime

workQueue

WorkQueue must be BlockingQueue BlockingQueue. When the number of threads in the thread pool exceeds its corePoolSize, the thread enters the blocking queue and waits. With workQueue, thread pools implement blocking.

In general, we should use bounded queues as much as possible, because using unbounded queues as work queues has the following effects on thread pools.

  1. When the number of threads in the thread pool reaches corePoolSize, new tasks wait in an unbounded queue, so the number of threads in the thread pool does not exceed corePoolSize.
  2. Due to 1, maximumPoolSize will be an invalid parameter when using unbounded queues.
  3. Because of 1 and 2, keepAliveTime will be an invalid parameter when using unbounded queues.
  4. More importantly, using an unbounded queue can exhaust system resources, while using a bounded queue can help prevent resource exhaustion, and even if using a bounded queue, try to keep the queue size within an appropriate range.

threadFactory

Create a thread factory. Through the custom thread factory, you can set a thread name with recognition for each new thread. Of course, you can also do more Settings for the thread more freely, such as setting all threads as daemons.

Executors Specifies the default threadFactory thread name in the static factory. The thread name is Pool-digital-thread-digital.

RejectedExecutionHandler

The saturation strategy of the thread pool. When the blocking queue is full and there are no idle worker threads, if the task continues to be submitted, a policy must be adopted to process the task. The thread pool provides four strategies:

  1. AbortPolicy: Directly throws an exception, the default policy.
  2. CallerRunsPolicy: Executes the task with the caller’s thread;
  3. DiscardOldestPolicy: Discards the most advanced task in the blocking queue and executes the current task.
  4. DiscardPolicy: Directly discards the task.

You can also implement the RejectedExecutionHandler interface according to application scenarios to customize saturation policies, such as logging or persistent storage of tasks that cannot be processed.

How thread pools work

  1. If fewer threads are currently running than corePoolSize, a new thread is created to perform the task (note that this step requires a global lock).
  2. If the running thread is equal to or more than corePoolSize, the task is added to BlockingQueue.
  3. If the task cannot be added to the BlockingQueue (the queue is full), a new thread is created to process the task.
  4. If you create a new thread will make the currently running thread beyond maximumPoolSize, task will be rejected, and call the RejectedExecutionHandler. RejectedExecution () method.

Submit a task

Execute () : Used to submit tasks that do not require a return value, so it is impossible to determine whether the task was successfully executed by the thread pool.

Submit () : Used to submit a task that needs to return a value. The thread pool returns an object of type Future that determines whether the task was successfully executed, and the return value can be retrieved from the Future’s get() method. The get() method blocks the current thread until the task is complete. Instead, get(long timeout, The TimeUnit unit method blocks the current thread for a while and then returns immediately, possibly without completing the task.

Closing the thread pool

A thread pool can be shutdown by calling its shutdown or shutdownNow methods. They work by iterating through worker threads in a thread pool and then interrupting them by calling the thread_by-thread_interrupt method, so tasks that cannot respond to interrupts may never be terminated. ShutdownNow first sets the state of the thread pool to STOP, then tries to STOP all threads executing or suspending tasks and returns the list of tasks waiting to be executed, while shutdown only sets the state of the thread pool to shutdown. It then interrupts all threads that are not executing tasks.

The isShutdown method returns true whenever either of the two shutdown methods is called. The thread pool is closed successfully when all tasks are closed, and calling isTerminaed returns true. Which method should be called to shutdown the thread pool depends on the nature of the task submitted to the pool. The shutdown method is usually called to shutdown the thread pool, or the shutdownNow method can be called if the task is not necessarily finished.

Configure thread pools properly

To properly configure thread pools, you must first analyze task characteristics from the following perspectives.

  • The nature of the task: CPU intensive task, IO intensive task, and hybrid task.
  • Task priority: high, medium and low.
  • Task execution time: long, medium and short.
  • Task dependencies: Whether they depend on other system resources, such as database connections.

Tasks of different natures can be handled separately by thread pools of different sizes.

  • Cpu-intensive tasks should be configured with the smallest possible threads, such as a thread pool of Ncpu+1 threads. Since IO intensive task threads are not executing tasks all the time, configure as many threads as possible, such as 2*Ncpu.
  • If a mixed task can be split into one CPU intensive task and one IO intensive task, the throughput of the split execution will be higher than that of the serial execution as long as the time difference between the two tasks is not too large. If the execution time of the two tasks is too different, there is no need to break them down. You can use runtime.getruntime ().availableProcessors() to get the number of cpus on the current device.
  • Tasks with different priorities can be processed using the PriorityBlockingQueue. It allows high-priority tasks to be executed first.
  • Tasks with different execution times can be assigned to thread pools of different sizes, or priority queues can be used to allow shorter tasks to be executed first.
  • A bounded queue is recommended. Bounded queues increase the stability and warning capabilities of the system and can be set as large as necessary, such as several thousand.
  • If we had set the queue to unbounded, the thread pool would have become so large that it would have overwhelmed memory and rendered the entire system unusable, not just the background tasks.

5. The queue synchronizer (AbstractQueuedSynchronizer)

The necessity of studying AQS

Queue synchronizer AbstractQueuedSynchronizer (hereinafter referred to as the synchronizer or AQS), is used to construct the lock or other synchronous component based framework, it USES a int member variable synchronous state, through the built-in FIFO queue to complete resources for thread line work. And the master of the package (Doug Lea) expects it to be the basis for most synchronization requirements.

How AQS are used and the design patterns therein

The main way to use AQS is inheritance. Subclasses manage synchronization state by inheriting AQS and implementing its abstract method. In AQS, an int state represents this state. You need to use the three methods provided by the synchronizer (getState(), setState(int newState), and compareAndSetState(int expect,int Update)), because they guarantee that state changes are safe.

/**
* The synchronization state.
*/
private volatile int state;
Copy the code

On the implementation, recommend a subclass is defined as the custom synchronous components of the static inner class, AQS itself does not implement any synchronization interfaces, it is simply defines the number of state synchronization acquisition and release methods for use by the custom synchronous components, synchronizer can support to exclusive access to sync, can also support to Shared access to sync, This makes it easy to implement different types of synchronization components (ReentrantLock, ReentrantReadWriteLock, CountDownLatch, etc.).

Synchronizers are key to implementing locks (which can be any synchronization component) and are aggregated in the implementation of locks. The relationship between the two can be understood as follows:

  • Locks are user-oriented, which defines the interface for the user to interact with the lock (for example, allowing two threads to access it in parallel) and hides implementation details.
  • Synchronizer is for the implementor of the lock, it simplifies the implementation of the lock, shielding the synchronization state management, thread queuing, waiting and wake up and other low-level operations. Locks and synchronizers nicely isolate areas of concern for consumers and implementors;
  • The implementer inherits the synchronizer and overwrites the specified methods, then combines the synchronizers in the implementation of the custom synchronization component and calls the template methods provided by the synchronizer that will invoke the user-overridden methods.

Template method pattern

Synchronizer design is based on the template method pattern. The intent of the template method pattern is to define the skeleton of an algorithm in an operation, while deferring the implementation of some steps to subclasses. The template approach allows subclasses to redefine specific steps of an algorithm without changing the structure of that algorithm. The most common ones are the various templates in the Spring framework.

Practical example

We opened a cake shop, and a cake shop can’t just sell one kind of cake, so we decided to sell cream cakes, cheesecake and mousse cakes first. All three cakes are made in the same way, including shaping, baking and coating the cake. So you can define an abstract cake model

public abstract class AbstractCake {

    protected abstract void shape(a);

    protected abstract void apply(a);

    protected abstract void brake(a);

    /** * template method */
    public final void run(a) {
        this.shape();
        this.apply();
        this.brake(); }}Copy the code

Then you can mass-produce three cakes

public class CheeseCake extends AbstractCake{

    @Override
    protected void shape(a) {
        System.out.println("Cheesecake look.");
    }

    @Override
    protected void apply(a) {
        System.out.println("Cheesecake spread.");
    }

    @Override
    protected void brake(a) {
        System.out.println("Cheesecake baking."); }}public class CreamCake extends AbstractCake{}public class MouseCake extends AbstractCake{
    
}


AbstractCake cake = new CheeseCake();
cake.run();

Copy the code

In this way, not only can we mass-produce three kinds of cakes, but if we expand in the future, we just need to inherit the abstract cake method, which is very convenient, and we are doing more and more profitable business every day.

And then one day, we found that there was a very simple cupcake that was selling very well, and it was just baked and it didn’t need any ingredients, and because it was easy to make, it was very profitable, so we wanted to produce this kind of cake. The problem is that the abstract cake defines the abstract smearing method, which means that the extended cake must implement the smearing method, which is a pain in the ass. How to do? We can change the original template to a template with hooks.

When making small cakes, flag is used to control whether to smear or not. Other existing cakes can be made as usual without any modification.

Methods in AQS

Template method

When implementing a custom synchronous component, the template methods provided by the synchronizer are called,

The template methods provided by these template method synchronizers fall into three basic categories: exclusive get and release synchronization state, shared get and release synchronization state, synchronization state, and query the status of waiting threads in the synchronization queue.

Rewritable methods

A method to access or modify synchronization state

When overriding a method specified by the synchronizer, you need to use the following three methods provided by the synchronizer to access or modify the synchronization state.

  • GetState () : obtains the current synchronization status.
  • SetState (int newState) : Sets the current synchronization status.
  • CompareAndSetState (int expect,int Update) : CAS is used to set the current state. This method ensures atomicity of the state setting.

CLH queue lock

CLH queue locks are Craig, Landin, and Hagersten (CLH) locks.

CLH queue locks are also scalable, high-performance, and fair spinlocks based on linked lists. The requisition thread spins only on local variables. It continuously polls the state of the precursor and terminates the spin if it finds that the precursor releases the lock.

When a thread needs to acquire a lock:

  1. Create a QNode with Locked set to true to obtain locks and myPred to reference its precursor

  1. Thread A calls the getAndSet method on the tail field, making itself the tail of the queue and getting A reference to its precursor, myPred

    Thread B needs to acquire the lock, and the same process starts again

  2. The thread rotates on the locked fields of the precursor until the precursor releases the lock (the precursor locks are locked == false)

  3. When a thread needs to release a lock, set the locked field of the current node to false and reclaim the precursor node

    As shown in the figure above, thread A releases the lock, and thread A obtains the lock when the locked field of thread A’s myPred points to the lock.

CLH queue locks have the advantage of low space complexity (if there are n threads, L locks, each thread only acquires one lock at a time, then the required storage space is O (L+ N), N threads have N MyNodes, L locks have L tail). CLH queue locks are commonly used in SMP architectures.

AQS in Java is a variant implementation of CLH queue locks.

The realization of the already

Reentrant of the lock

Reentry means that any thread that has acquired a lock can acquire the lock again without being blocked by the lock. The implementation of this feature needs to solve the following two problems.

  1. The thread acquires the lock again. The lock needs to identify whether the thread acquiring the lock is the thread currently occupying the lock, and if so, it is successfully acquired again.
  2. The final release of the lock. The thread acquires the lock n times, and after releasing the lock for the NTH time, other threads can acquire the lock. The final release of the lock requires the lock to increment the count for the acquisition. The count represents the number of times the current lock has been acquired repeatedly. When the lock is released, the count decreases.

The nonfairTryAcquire method adds the processing logic for acquiring the synchronization state again: the synchronization state is successfully acquired by determining whether the current thread is the thread acquiring the lock. If the thread acquiring the lock requests again, the synchronization state value is increased and returns true, indicating that the synchronization state is successfully acquired. Synchronization status represents the number of times a lock has been repeatedly acquired by a thread.

If the lock is acquired n times, then the previous (n-1) tryRelease(int Releases) method must return false, and only return true if the synchronization state is fully released. As you can see, the method conditions the final release on whether the synchronization status is 0. When the synchronization status is 0, the owning thread is set to NULL and returns true, indicating that the release is successful.

Fair and unfair locks

In the constructor of ReentrantLock, the default no-argument constructor creates the Sync object as NonfairSync, which is an “unfair lock”; The other constructor, ReentrantLock(Boolean fair), will create the Sync object as FairSync if it is passed true.

NonfairTryAcquire (int acquires) method. For an unfair lock, if the CAS synchronization status is set successfully, the current thread has acquired the lock. The only difference between the tryAcquire method and nonfairTryAcquire(int acquires) is that the HasqueuedToraise () method, which joined the synchronous queue to determine whether the current node has a precursor node, If this method returns true, it means that a thread requested the lock earlier than the current thread and therefore needs to wait for the precursor thread to acquire and release the lock before continuing to acquire the lock.

6. In-depth understanding of concurrent programming and summarizing

MM Fundamentals – Computer Principles

The Java Memory Model is also known as the Java Memory Model, or JMM. The JMM defines how the Java Virtual Machine (JVM) works in computer memory (RAM). The JVM is the entire computer virtual model, so the JMM belongs to the JVM. It was refactored in Java version 1.5, which is still used today. The problems encountered by the Jmm are similar to those encountered by modern computers.

There are many similarities between the concurrency problem in physical computer and the situation in virtual machine. The solution of the concurrency problem in physical machine also has considerable reference significance for the realization of virtual machine.

According to Jeff Dean’s presentation at Google’s All-hands Engineering Conference

Computers need different response times to do some of the basic things we normally do.

(The following examples are illustrative only and do not represent the actual situation.)

How long does it take to read 1M ints from memory and add them up by the CPU?

A simple calculation is performed on the CPU. If 1 MB of data is stored in Java and the int type is 32 bits (4 bytes), a total of 1024*1024/4 = 262144, the CPU calculation time is as follows: 262144 0.6 = 157 286 nanoseconds, and we know that it takes 250,000 nanoseconds to read 1M data from memory. This is not a small difference (100,000 nanoseconds is enough time for the CPU to execute nearly 200,000 instructions), but it is still an order of magnitude. However, the absence of any caching mechanism means that each number needs to be read from memory, so it takes 100 nanoseconds for the CPU to read one memory, and 262144 integers need 262144100+250000 = 26464 400 nanoseconds to read from memory to CPU plus the computation time. That’s an order of magnitude difference.

And in reality, the vast majority of computing tasks can not only rely on the processor “calculation” can be completed, the processor must at least interact with memory, such as reading operational data, storage of operational results, this I/O operation is basically impossible to eliminate (can not only rely on registers to complete all computing tasks). The speed of CPU and memory in early computers was about the same, but in modern computers, the speed of CPU instruction far outpaces the speed of memory access. So modern computer systems have to add a buffer between memory and the processor, a Cache that reads and writes as fast as possible: Copying the data needed for an operation to the cache allows the operation to proceed quickly. When the operation is complete, it is synchronized from the cache back to memory so that the processor does not have to wait for slow memory reads and writes.

In a computer system, the register partition is the L0 level cache, followed by L1, L2, L3 (memory, local disk, remote storage). The higher up the cache, the less space, the faster, and the more expensive it is; The further down you go, the more storage, the slower, and the cheaper. From top to bottom, each layer can be regarded as the cache of the next layer, that is: register L0 is the cache of L1 level cache, L1 is L2 cache, and so on; The data in each layer comes from the next layer, so the data in each layer is a subset of the data in the next layer.

On modern CPUS, L0, L1, L2, and L3 are generally integrated in the CPU, and L1 is also divided into first-level Data Cache (D-cache, L1d) and first-level Instruction Cache (I-cache, L1i). Respectively for storing data and executing data instruction decoding. Each core has independent processing units, controllers, registers, L1, L2 caches, and then multiple cores of a CPU share the final CPU cache, L3

Java Memory Model (JMM)

From an abstract point of view, JMM defines an abstract relationship between threads and Main Memory: Shared variables between threads are stored in Main Memory, and each thread has a private Local Memory where it stores copies of shared variables to read/write. Local memory is an abstraction of the JMM and does not really exist. It covers caching, write buffers, registers, and other hardware and compiler optimizations.

visibility

Visibility means that when multiple threads access the same variable and one thread changes the value of the variable, other threads can immediately see the changed value.

Since threads must do all operations on variables in working memory and cannot read or write variables directly from main memory, for shared variables V, they are first in their own working memory and then synchronized to main memory. But it will not be brushed in time to main memory, but there will be a certain time difference. Obviously, thread A’s operations on variable V are not visible to thread B.

To solve the problem of shared object visibility, we can use the volatile keyword or locking.

atomic

Atomicity: An operation or operations are either all performed without interruption by any factor, or none at all.

We all know is the allocation of CPU resources are a thread, and time-sharing is invoked, the operating system allows a process to perform a short period of time, such as 50 milliseconds, over 50 milliseconds operating system will choose a process to perform again (we called “task switching”), the 50 milliseconds referred to as the “time slice”. Most tasks switch after the end of the time segment.

So why is thread switching a bug? Because the operating system does task switching, it can happen at the end of any CPU instruction! Note that this is CPU instruction, CPU instruction, CPU instruction, not a statement in a high-level language. Count++, for example, is a sentence in Java, but a statement in a high-level language often requires multiple CPU instructions to complete. Count++ contains at least three CPU instructions!

Volatile,

Volatile features

A single read/write to a volatile variable can be thought of as synchronizing these individual reads/writes using the same lock

Can be seen as

So volatile variables themselves have the following properties:

  • Visibility. A read of a volatile variable always sees the last write (by any thread) to that volatile variable.
  • Atomicity: Atomicity of read/write to any single volatile variable, but not of compound operations such as volatile++.

Volatile although can ensure timely execution of the brush variables to the main memory, but for count++ to the non atomicity, multiple instruction situation, because of the thread, as soon as the thread A count = 0 is loaded into the working memory, thread B you can begin to work, this will lead to thread the result of the execution of the A and B are 1, are written in the main memory, The value of main memory is still 1 not 2

How volatile is implemented

Volatile variables have a “lock:” prefix.

Lock prefix, Lock is not a memory barrier, but it can perform a similar function. Lock locks the CPU bus and cache, which can be understood as a CPU instruction level Lock.

At the same time, this instruction will write the current processor cache row directly to the system memory, and this write back to memory will invalidate the other CPU cache of the address of the data.

The realization principle of synchronized

Synchronized implementations in the JVM are based on entering and exiting Monitor objects to implement method synchronization and code block synchronization. Although the implementation details vary, they are implemented through pairs of MonitorEnter and MonitorExit directives.

For synchronized blocks, the MonitorEnter directive inserts at the beginning of the synchronized code block, while the monitorExit directive inserts at the end of the method and at the exception. The JVM guarantees that each MonitorEnter must have a monitorExit. In general, when the code executes to this instruction, it will attempt to acquire ownership of the object Monitor, i.e. try to acquire the lock on the object:

  1. If monitor’s number of entries is 0, the thread enters Monitor, then sets the number of entries to 1, and the thread is the owner of Monitor.
  2. If the thread already owns the monitor and simply re-enters, the number of entries into the monitor is increased by one.
  3. If another thread has occupied monitor, the thread blocks until the number of monitor entries is zero, and then tries again to acquire ownership of monitor.

Monitorenter and Monitorexit do not synchronize the synchronized methods. The constant pool of monitorenter and Monitorexit contains the ACC_SYNCHRONIZED identifier.

The JVM implements method synchronization based on this identifier: When a method is called, the calling instruction will check whether the ACC_SYNCHRONIZED access flag of the method is set. If so, the executing thread will acquire monitor first and execute the method body only after the method is successfully acquired. Monitor will be released after the method is executed. During method execution, the same Monitor object is no longer available to any other thread.

Synchronized locks are stored in a Java object header consisting of a Mark Word and a Klass Pointer:

  1. Mark Word stores synchronization state, identity, Hashcode, GC state, and so on.
  2. Klass Pointer Stores a type pointer to an object that points to its class metadata

In addition, for an array, there is a record of the length of the array.

The lock information is stored in the mark Word of the object. The default data in the MarkWord is the HashCode and other information of the object.

However, it will change with the running of the object. Different lock states correspond to different record storage modes

Learn about locks

The state of the lock

There are four types of lock, bias lock, lightweight lock and heavyweight lock, which escalate with competition. Locks can be upgraded but not degraded, to improve the efficiency of acquiring and releasing locks.

Biased locking

Background: In most cases, locks are not contested by multiple threads, and are always acquired by the same thread multiple times. Biased locks are introduced to reduce unnecessary CAS operations in order to reduce the cost of locks acquired by threads.

Biased locking, just as its name implies, it will tend to the first visit to lock the thread, if in the process of running, only one thread synchronization lock access, there is no multithreading contention, the thread is not to need to trigger synchronization, reduce some of the CAS lock/unlock operation (such as some of the waiting queue CAS operation), in this case, will give the thread a biased locking. If, while running, another thread preempts the lock, the thread holding the biased lock is suspended, and the JVM removes the biased lock from its body, reverting the lock to a standard lightweight lock. It further improves the performance of the program by eliminating the synchronization primitive in the case of uncontested resources.

Biased lock acquisition process:
  • Step 1 Check whether the bias lock identifier is set to 1 and the lock flag bit is set to 01 in the Mark Word, and confirm that it is in the bias state.
  • Step 2. If it is biased, test whether the thread ID points to the current thread. If so, go to Step 5; otherwise, go to Step 3.
  • Step 3 If the thread ID does not point to the current thread, the CAS operation is used to compete for the lock. If the competition succeeds, set the thread ID in Mark Word to the current thread ID, and then perform 5. If the competition fails, go to 4.
  • Step 4 If the CAS fails to obtain the biased lock, a race occurs. When the safepoint is reached, the thread that acquired the bias lock is suspended, the bias lock is upgraded to a lightweight lock, and the thread blocked at the safepoint continues to execute the synchronization code. (Unlock bias causes stop the word)
  • Step 5 Execute the synchronization code.

Partial lock release:

The undo of bias locks is mentioned in step 4 above. Biased lock Only when other threads try to compete for biased lock, the thread holding biased lock will release biased lock, the thread will not actively release biased lock. The revocation of biased lock needs to wait for the global safe point (at which no bytecode is being executed). It will first suspend the thread with biased lock and determine whether the lock object is locked. After revocation of biased lock, it will return to the state of unlocked (flag bit “01”) or lightweight lock (flag bit “00”).

Application scenarios of biased lock

While performing synchronized block, always only one thread in it has not performed before releasing the lock, no other threads to execute the synchronized block, the lock without competition, once the competition will upgrade for lightweight locks, upgrade for lightweight locked when you need to reverse bias, revocation of biased locking time will lead to stop the word operation;

In lock competition, biased lock will do a lot of extra operations, especially the cancellation of biased lock will lead to the entry of the safe point, which will lead to STW, resulting in performance degradation, in this case should be disabled.

The JVM turns bias locking on/off

Open the biased locking: – XX: XX: + UseBiasedLocking BiasedLockingStartupDelay = 0

Disable biased locking: -xx: -usebiasedlocking

Lightweight lock

Lightweight locks are upgraded from biased locks. Biased locks work when one thread enters a synchronized block. Biased locks are upgraded to lightweight locks when the second thread joins the lock contention.

Lightweight lock locking process:
  1. When the code enters the synchronization block, if the synchronization object Lock state is lock-free and bias is not allowed (Lock flag is “01” state, whether bias Lock is “0”), the virtual machine will first establish a space named Lock Record in the stack frame of the current thread. Store product of product product product product product product product product product product product product product product product product product product
  2. Copy the Mark Word from the copy object header to the lock record.
  3. After the copy is successful, the VM uses the CAS operation to try to update the Mark Word of the object to a pointer to the Lock Record, and the owner pointer in the Lock Record to the Object Mark Word. If the update succeeds, go to Step 4; otherwise, go to Step 5.
  4. If the update succeeds, the thread owns the lock on the object, and the object’s Mark Word lock bit is set to 00, indicating that the object is in a lightweight locked state.
  5. If the update fails, the virtual machine first checks to see if the object’s Mark Word points to the current thread’s stack frame. If it does, the current thread has the lock on the object and can proceed directly to the synchronization block. Otherwise, it means that multiple threads compete for the lock, so it will spin to wait for the lock, after a certain number of times, still do not acquire the lock object. The heavyweight thread points to the rival thread, which also blocks waiting for the lightweight thread to wake up after releasing the lock. The status value of the lock flag changes to “10”, and the Mark Word stores Pointers to heavyweight locks (mutex), followed by threads waiting for the lock to block.
spinlocks
The principle of

Spinlocks principle is very simple, if the thread holding the lock can lock is released in a very short time resources, and the thread lock wait for competition there is no need to do between kernel mode and user mode switch into the block pending state, they just need to wait for a while (spin), such as thread holding the lock immediately after releasing the lock locks, thus avoiding the consumption of user and kernel thread switching.

However, thread spin consumes CPU, which means that the CPU is doing idle work. Threads can’t keep doing idle work, so they need to set a maximum spin wait time.

If the thread holding the lock executes for longer than the maximum spin wait time and does not release the lock, other threads contending for the lock still cannot acquire the lock within the maximum wait time, then the contending thread will stop spinning and enter the blocking state.

Advantages and disadvantages of spin locks

Spinlocks reduce thread blocking as much as possible, which is a big performance improvement for blocks that are not locked and have a very short lock time, because the cost of spinning is less than the cost of blocking and suspending the thread!

But if the lock of the competition is intense, or thread holding the lock need long time to occupy the lock synchronization block, this time is not suitable for using a spin lock, because of the spin lock before acquiring a lock is CPU doing this all the time, for the XX XX, spin thread consumption is greater than the thread block suspend operation consumption, other threads to CPU and can’t get to the CPU, Waste of CPU.

Spin lock time threshold

The purpose of a spin lock is to hold CPU resources until the lock is acquired. But how do you choose when to execute the spin? If the spin execution time is too long, a large number of threads in the spin state will occupy CPU resources, which will affect the overall system performance. So the number of spins is important.

JVM for the choice, the number of spin jdk1.5 default is 10 times, in 1.6 introduces adaptive spin locks, adaptive spin lock means that the spin time is not fixed, but by the last spin in the same lock lock time, and the owner of the state to decide, basic thought a thread context switching time is the best one.

-xx :+UseSpinning enables spin lock in JDK1.6. After JDK1.7, this parameter is removed and controlled by JVM.

Comparison of different locks

7. Meet often

What is the difference between sychronied and static methods? What is visibility?

An object lock is used on an object instance method, or on an object instance. A class lock is used on a class static method, or on a class class object. As we know, there can be many object instances of a class, but each class has only one class object, so the object locks of different object instances are not interfering with each other, but each class has only one class lock.

However, it is important to note that class locking is only a concept, not a real thing. In fact, class locking is the corresponding class object of each class. Class and object locks are also non-interfering.

Visibility means that when multiple threads access the same variable and one thread changes the value of the variable, other threads can immediately see the changed value.

Since threads must do all operations on variables in working memory and cannot read or write variables directly from main memory, for shared variables V, they are first in their own working memory and then synchronized to main memory. But it will not be brushed in time to main memory, but there will be a certain time difference. Obviously, thread A’s operations on variable V are not visible to thread B.

To solve the problem of shared object visibility, we can use the volatile keyword or locking.

What are the types of locks?

Principle of CAS lockless programming

The CAS() instruction is supported by most current processors, but the algorithm implemented by each vendor is different. Each CAS operation contains three operators: A memory address V, A desired value A, and A new value b. If the value stored at the address is equal to the desired value A, the value of the address is assigned to the new value b. Otherwise, no operation is performed.

The basic idea of CAS is that if the value at this address is the same as the expected value, it assigns it a new value, otherwise it does nothing, but returns what the original value was. Cyclic CAS means that the CAS operation is repeated in a loop until it succeeds.

There are also three major problems with CAS.

Implementation principle of ReentrantLock.

A thread can repeatedly enter any block of code synchronized with a lock it already owns. Synchronized and ReentrantLock are reentrant locks. In terms of implementation, when a thread obtains a lock, it decides that if the thread that obtains the lock is itself, it simply accumulates the counter. Every time the lock is released, it accumulates the counter until the calculator returns to zero, indicating that the thread has completely released the lock.

The bottom layer is the use of JUC AQS to achieve.

AQS Principle (Xiaomi jingdong)

AQS is a basic framework for building locks and other synchronization components, such as ReentrantLock, ReentrantReadWriteLock, and CountDownLatch. It uses an int member variable to indicate synchronization status, and queues the resource acquisition threads through a built-in FIFO queue. It is a variant implementation of CLH queue locks. It can implement two synchronization modes: exclusive and shared.

The primary use of AQS is inheritance. Subclasses manage synchronization state by inheritingAQS and implementing its abstract methods. The synchronizer design is based on the template method pattern, so if we want to implement our own synchronization utility class, we need to override several of these rewritable methods, such as tryAcquire, tryReleaseShared, and so on.

The purpose of this design is that synchronous components (such as locks) are consumer-oriented. It defines the interface for the user to interact with the synchronous components (such as allowing two threads to access them in parallel) and hides implementation details. Synchronizer is for the implementor of the lock, it simplifies the implementation of the lock, shielding the synchronization state management, thread queuing, waiting and wake up and other low-level operations. This provides a good separation of the areas that consumers and implementers need to focus on.

Internally, AQS maintains a shared resource state, with a built-in FIFO to queue up threads to acquire resources. The queue consists of nodes one by one, each of which maintains a prev reference and a next reference to its precursor and successor, respectively, forming a double-ended bidirectional linked list.

Principles of Synchronized and the difference between Synchronized and ReentrantLock. (360).

Principle of synchronized (this) : Involves two commands: monitorenter, Monitorexit; Monitorenter and Monitorexit do not synchronize the synchronized methods. The constant pool of monitorenter and Monitorexit contains the ACC_SYNCHRONIZED identifier.

The JVM implements method synchronization based on this identifier: When a method is called, the calling instruction will check whether the ACC_SYNCHRONIZED access flag of the method is set. If so, the executing thread will acquire monitor first and execute the method body only after the method is successfully acquired. Monitor will be released after the method is executed. During method execution, the same Monitor object is no longer available to any other thread.

What optimization did Synchronized do (JD)

  1. Techniques such as spin locking, adaptive spin locking, lock elimination, lock coarser, biased locking, lightweight locking, escape analysis are introduced to reduce the overhead of locking operations.
  • Escape analysis: If it is proved that an object cannot escape from a method or thread, it can be optimized for this variable:
  1. Synchronization Elimination. The Elimination of synchronization of an object variable if it does not escape the thread.

  2. Lock elimination and coarsening

  • Lock elimination: The virtual machine’s runtime compiler removes locks at run time if it detects that shared data contention is not possible on code that requires synchronization.

  • Lock coarsening: To combine adjacent blocks of code with the same lock.

    Eliminating meaningless lock acquisition and release can improve program performance.

Synchronized static and non-static locks

An object lock is used on an object instance method, or on an object instance. A class lock is used on a class static method, or on a class class object. As we know, there can be many object instances of a class, but each class has only one class object, so the object locks of different object instances are not interfering with each other, but each class has only one class lock.

However, it is important to note that class locking is only a concept, not a real thing. In fact, class locking is the corresponding class object of each class. Class and object locks are also non-interfering.

Does volatile keep threads safe? What’s the role on the DCL?

Volatile guarantees the visibility and order of the variable being modified. Volatile guarantees that, in singleton mode, the order of creation of an object must be the following, thus ensuring that instance is either null or fully initialized:

  1. Allocating memory space
  2. Instantiate the object instance
  3. Reference instance to the allocated memory space. Instance now has a memory address and is no longer null

What’s the difference between volatile and synchronize? (B station xiaomi jingdong)

Volatile is the lightest synchronization mechanism. Volatile ensures the visibility of different threads operating on the variable. When one thread changes the value of a variable, the new value is immediately visible to other threads. But volatile does not guarantee atomicity, so write composition in multiple threads can cause thread-safety problems.

The keyword synchronized can be used to modify methods or in the form of synchronized blocks. It mainly ensures that multiple threads can only have one thread in a method or synchronized block at the same time. It ensures the visibility and exclusivity of thread access to variables, also known as the built-in lock mechanism.

What is a daemon thread? How do you exit a thread?

A Daemon thread is a support thread because it is used primarily for background scheduling and support work within a program. This means that a Java virtual machine will exit when there are no non-Daemon threads in the machine. Threads can be set to Daemon threads by calling thread.setdaemon (true). We don’t usually use it, for example garbage collection threads are Daemon threads.

Thread termination:

Either the run execution completed, or an unhandled exception was thrown causing the thread to terminate prematurely.

The apis for suspending, resuming, and stopping threads are ****suspend(), resume(), and stop()****. But these apis are out of date, which means they are not recommended. As a result, the program may work in an indeterminate state.

A safe interrupt isInterrupted by calling the interrupt() method of thread A, or by calling isInterrupted() to see if the interrupted thread isInterrupted. You can also call the static thread.interrupted () method to check whether the current Thread has been interrupted, but thread.interrupted () also changes the interrupt flag bit to false.

How does the wait thread wake it up? (Oriental Headline)

Yield () method: Causes the current thread to yield CPU ownership, but the yield time is not specified. Lock resources are not released. All threads that perform yield() are likely to be selected by the operating system and executed immediately after they enter the ready state.

Either yield() or sleep() will not release the lock held by the current thread.

After the wait() method is called, the lock held by the current thread is released, and after the current thread is awakened, the lock is contested again. The code following the wait method is executed only after the lock is contended.

Wait is usually used for interthread interactions, sleep is usually used to pause execution, and yield() causes the current thread to relinquch CPU ownership.

Wait threads wake up using notify/notifyAll().

Is sleep interruptible? (millet)

Sleep itself supports interrupts, and if a thread is interrupted during a sleep, an interrupt exception is thrown.

Thread life cycle.

There are six states of threads in Java:

  1. Initial (NEW) : A NEW thread object is created, but the start() method has not yet been called.

  2. RUNNABLE: Java threads refer to the ready and running states as “RUNNABLE.”

    After the thread object is created, other threads, such as the main thread, call the start() method of the object. A thread in this state is in the runnable thread pool, waiting to be selected by thread scheduler for CPU usage, and is in the ready state. A thread in the ready state becomes running after obtaining a CPU time slice.

  3. BLOCKED: A thread is BLOCKED from a lock.

  4. WAITING: a thread in this state is WAITING for some specific action (notification or interrupt) from another thread.

  5. TIMED_WAITING: This state is different from WAITING. It can return after a specified time.

  6. TERMINATED: indicates that the thread is TERMINATED.

What is a ThreadLocal?

ThreadLocal is a special kind of variable in Java. ThreadLocal provides each thread with a copy of a variable so that each thread is not accessing the same object at any one time, thus isolating multiple threads from sharing data.

Internally, each thread has an internal ThreadLocalMap that holds copies of variables owned by each thread.

Thread pool fundamentals

There are three benefits to using thread pools properly during development.

First: reduce resource consumption. Second: improve response speed. Third: improve thread manageability.

1) If there are fewer threads currently running than corePoolSize, a new thread is created to perform the task (note that this step requires a global lock).

2) Add the task to BlockingQueue if the number of running threads is equal to or greater than corePoolSize.

3) If the task cannot be added to the BlockingQueue (queue is full), create a new thread to process the task.

4) if you create a new thread will make the currently running thread beyond maximumPoolSize, task will be rejected, and call the RejectedExecutionHandler. RejectedExecution () method.

I have three threads, T1, T2, T3. How do I make sure they execute in sequence?

This can be done using the Join method.