Hello, I’m Dabin. Recently in the interview, read a lot of experience, take time to Java concurrent programming common interview questions summarized, here to share with you ~

The thread pool

Thread pool: A pool that manages threads.

Why use thread pools?

  • Reduce resource consumption. Reduce the cost of thread creation and destruction by reusing created threads.
  • Improve response speed. When a task arrives, it can be executed immediately without waiting for the thread to be created.
  • Improve thread manageability. Unified management of threads, avoid the system to create a large number of similar threads resulting in memory consumption.
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);
Copy the code

How does thread pool execution work?

Creating a new thread requires acquiring a global lock, which is designed to be avoided as much as possible. When ThreadPoolExecutor has warmed up (the number of threads currently running is greater than or equal to corePoolSize), most of the submitted tasks are placed in BlockingQueue.

To visualize thread pool execution, use an analogy:

  • Core threads are compared to company employees
  • Non-core threads are compared to outsourced employees
  • Blocking queues are compared to requirements pools
  • Submitting tasks is like making a request

What are the thread pool parameters?

ThreadPoolExecutor’s generic constructor:

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);
Copy the code
  • CorePoolSize: When there is a new task, if the number of threads in the thread pool does not reach the base size of the thread pool, a new thread is created to execute the task, otherwise the task is put on a blocking queue. When the number of threads alive in the thread pool is always greater than corePoolSize, you should consider increasing corePoolSize.

  • MaximumPoolSize: When the blocking queue fills up, if the number of threads in the thread pool does not exceed the maximum number of threads, a new thread is created to run the task. Otherwise, the new task is processed according to the reject policy. Non-core threads are similar to temporarily borrowed resources and should exit after the idle time exceeds keepAliveTime to avoid wasting resources.

  • BlockingQueue: Stores tasks waiting to run.

  • KeepAliveTime: indicates the keepAliveTime of non-core threads after they are idle. This parameter is valid only for non-core threads. If the value is set to 0, redundant idle threads are terminated immediately.

  • TimeUnit: TimeUnit

    TimeUnit.DAYS
    TimeUnit.HOURS
    TimeUnit.MINUTES
    TimeUnit.SECONDS
    TimeUnit.MILLISECONDS
    TimeUnit.MICROSECONDS
    TimeUnit.NANOSECONDS
    Copy the code
  • ThreadFactory: whenever a thread pool creates a new thread, it does so through the ThreadFactory method. There is only one method defined in ThreadFactory, newThread, which is called whenever the thread pool needs to create a newThread.

    public class MyThreadFactory implements ThreadFactory {
        private final String poolName;
        
        public MyThreadFactory(String poolName) {
            this.poolName = poolName;
        }
        
        public Thread newThread(Runnable runnable) {
            return new MyAppThread(runnable, poolName);// Pass the thread pool name to the constructor to distinguish between threads in different thread pools}}Copy the code
  • RejectedExecutionHandler: Handle new tasks according to the rejection policy when both queues and thread pools are full.

    AbortPolicy: the default strategy, direct selling RejectedExecutionException DiscardPolicy: don't handle, discarding DiscardOldestPolicy directly: Discards the task at the head of the queue and executes the current task CallerRunsPolicy: handled by the calling threadCopy the code

How to set the thread pool size?

If the number of threads in the thread pool is too small and a large number of requests need to be processed, the system responds slowly and experience is affected. In addition, a large number of tasks may accumulate in the task queue, resulting in OOM.

If the number of threads in the thread pool is too large, a large number of threads may compete for CPU resources at the same time, which will lead to a large number of context switches (the CPU allocates time slices to the thread and saves the state when the thread runs out of CPU time slices so that it can continue running next time), thereby increasing the execution time of the thread and affecting the overall execution efficiency.

Cpu-intensive tasks (N+1) : The number of threads can be set to N (number of CPU cores) +1. One more thread is used to prevent the impact of task suspension caused by some reasons (thread blocking, such as I/O operation, waiting for lock, thread sleep). Once a thread is blocked, CPU resources are freed, and in this case the extra thread can take full advantage of the CPU’s free time.

I/ O-intensive task (2N) : The system spends most of its time processing I/O operations, while the thread waits for I/O operations to block, freeing up CPU resources and freeing up the CPU for another thread to use. Therefore, you can configure more threads for I/ O-intensive tasks. The calculation method is as follows: Optimal number of threads = Number of CPU cores x (1/CPU usage) = Number of CPU cores x (1 + (I/O time /CPU time)). Generally, you can set this parameter to 2N

What are the types of thread pools? Applicable scenarios?

Common thread pools are FixedThreadPool, SingleThreadExecutor, CachedThreadPool, and ScheduledThreadPool. These are examples of ExecutorService.

FixedThreadPool

A thread pool with a fixed number of threads. No more than nThreads can be active at any one time.

public static ExecutorService newFixedThreadPool(int nThreads) {
	return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
Copy the code

Using unbounded queue LinkedBlockingQueue (queue capacity of Integer. MAX_VALUE), the running thread pool can’t refuse to task, namely don’t call RejectedExecutionHandler rejectedExecution () method.

MaxThreadPoolSize is an invalid parameter, so set its value to the same as coreThreadPoolSize.

KeepAliveTime is invalid parameter, is set to 0 l, because all threads in this thread pool is the core thread, core thread will not be recycled (unless set the executor. AllowCoreThreadTimeOut (true)).

Application scenario: This mode is suitable for processing CPU-intensive tasks, ensuring that the CPU is used by workers for a long time and allocating as few threads as possible. That is, it is suitable for executing long-term tasks. Note that FixedThreadPool does not reject tasks and will result in OOM if there are too many tasks.

SingleThreadExecutor

A thread pool with only one thread.

public static ExecutionService newSingleThreadExecutor() {
	return new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
Copy the code

Use the unbounded queue LinkedBlockingQueue. The thread pool has only one running thread, new tasks are put into the work queue, and the thread loops to fetch the task execution from the queue when it finishes processing the task. Ensure that tasks are performed sequentially.

Application scenario: This mode applies to the scenario where tasks are executed one by one. This will also result in OOM if there are too many tasks.

CachedThreadPool

Create a thread pool for new threads as needed.

public static ExecutorService newCachedThreadPool() {
	return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}
Copy the code

CachedThreadPool keeps creating new threads if the main thread is submitting tasks faster than the thread can process them. In extreme cases, this can exhaust CPU and memory resources.

SynchronousQueue (Runnable Task) is used as the pool work queue. When there are idle threads in the pool, the task submitted by synchronousQueue.offer (Runnable task) is processed by the idle thread; otherwise, a new thread is created to process the task.

Application scenario: Used to execute a large number of short-term tasks concurrently. CachedThreadPool allows the number of threads to be created as Integer.MAX_VALUE, which may create a large number of threads, resulting in OOM.

ScheduledThreadPoolExecutor

Run the task after a given delay, or execute the task periodically. It is rarely used in real projects because there are other options such as Quartz.

DelayQueue encapsulates a PriorityQueue. PriorityQueue sorts the tasks in the queue, and the earlier tasks are executed first (i.e. ScheduledFutureTask whose time variable is smaller). If time is the same, the task submitted first is executed (the squenceNumber of ScheduledFutureTask is executed first if the value of the squenceNumber variable is smaller).

Procedure for executing periodic tasks:

  1. The thread fromDelayQueueTo obtain the expiredScheduledFutureTask (DelayQueue. Take ()). A task due isScheduledFutureTaskIs greater than or equal to the current system time.
  2. To perform thisScheduledFutureTask;
  3. Modify theScheduledFutureTaskThe time variable of is the next time to be executed;
  4. Change this after timeScheduledFutureTaskPut it backDelayQueue(DelayQueue.add()).

Application scenario: Periodically execute tasks and limit the number of threads.

Process thread

A process is an application running in memory. Each process has its own independent memory space. Multiple threads can be started in a process. A thread is a smaller unit of execution than a process. It is an independent flow of control within a process. A process can start multiple threads, each performing a different task in parallel.

The life cycle of a thread

Initial (NEW) : The thread is built without calling start().

RUNNABLE: includes the ready and running states of the operating system.

BLOCKED: It is typically passive, unable to get a resource while grabbing it, and passively hangs in memory waiting for the resource to be released to wake it up. A blocked thread frees CPU, not memory.

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

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

TERMINATED: indicates that the thread is TERMINATED.

Image credit: The Art of Java Concurrent programming

What about thread interrupts?

A thread interrupt is a thread that is interrupted by another thread while it is running. Stop is forced by the system to terminate the thread, and the thread interrupt is to send an interrupt signal to the target thread, if the target thread does not receive the signal of thread interrupt and end the thread, the thread will not terminate, the specific whether to exit or execute other logic depends on the target thread.

There are three important ways to interrupt a thread:

1, Java. Lang. Thread# interrupt

Call the target thread’s interrupt() method to signal an interrupt to the target thread, which is marked with an interrupt.

2, Java. Lang. Thread# isInterrupted ()

Determines whether the target thread is interrupted. The interrupt flag is not cleared.

3, Java. Lang. Thread# interrupted

Determines whether the target thread is interrupted and clears the interrupt flag.

private static void test2(a) {
    Thread thread = new Thread(() -> {
        while (true) {
            Thread.yield();

            // The response is interrupted
            if (Thread.currentThread().isInterrupted()) {
                System.out.println("Java technology stack thread interrupted, program exits.");
                return; }}}); thread.start(); thread.interrupt(); }Copy the code

What are the ways to create a thread?

  • Create multiple threads by extending the Thread class
  • By implementing the Runnable interface to create multiple threads, resources can be shared between threads
  • Implement the Callable interface and create threads through the FutureTask interface.
  • Use the Executor framework to create thread pools.

Inheriting Thread to create Thread code is as follows. The run() method is called back by the JVM after the operating system thread is created. It cannot be called manually, which is equivalent to calling ordinary methods.

/ * * *@author: Programmer Dabin *@time: the 2021-09-11 10:15 * /
public class MyThread extends Thread {
    public MyThread(a) {}@Override
    public void run(a) {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread() + ":"+ i); }}public static void main(String[] args) {
        MyThread mThread1 = new MyThread();
        MyThread mThread2 = new MyThread();
        MyThread myThread3 = newMyThread(); mThread1.start(); mThread2.start(); myThread3.start(); }}Copy the code

Runnable creates thread code:

/ * * *@author: Programmer Dabin *@time: the 2021-09-11 10:04 * /
public class RunnableTest {
    public static  void main(String[] args){
        Runnable1 r = new Runnable1();
        Thread thread = new Thread(r);
        thread.start();
        System.out.println("Main thread: ["+Thread.currentThread().getName()+"]"); }}class Runnable1 implements Runnable{
    @Override
    public void run(a) {
        System.out.println("Current thread:"+Thread.currentThread().getName()); }}Copy the code

Implementing the Runnable interface has advantages over inheriting the Thread class:

  1. Resource sharing, suitable for multiple threads of the same program code to handle the same resource
  2. You can avoid the restriction of single inheritance in Java
  3. Thread pools can only be placed into classes that implement Runable or Callable, not directly into classes that inherit threads

Callable creates thread code:

/ * * *@author: Programmer Dabin *@time: 1 * / 2021-09-11
public class CallableTest {
    public static void main(String[] args) {
        Callable1 c = new Callable1();

        // The result of the asynchronous calculation
        FutureTask<Integer> result = new FutureTask<>(c);

        new Thread(result).start();

        try {
            // Wait for the task to complete and return the result
            int sum = result.get();
            System.out.println(sum);
        } catch(InterruptedException | ExecutionException e) { e.printStackTrace(); }}}class Callable1 implements Callable<Integer> {

    @Override
    public Integer call(a) throws Exception {
        int sum = 0;

        for (int i = 0; i <= 100; i++) {
            sum += i;
        }
        returnsum; }}Copy the code

Create thread code using Executor:

/ * * *@author: Programmer Dabin *@time: the 2021-09-11 10:44 * /
public class ExecutorsTest {
    public static void main(String[] args) {
        ExecutorService instance is disabled for production, and the thread pool needs to be created manually
        ExecutorService executorService = Executors.newCachedThreadPool();
        // Submit the task
        executorService.submit(newRunnableDemo()); }}class RunnableDemo implements Runnable {
    @Override
    public void run(a) {
        System.out.println("DaBin"); }}Copy the code

What is a thread deadlock?

Multiple threads are blocked at the same time, and one or all of them are waiting for a resource to be released. Because the thread is blocked indefinitely, the program cannot terminate normally.

As shown in the figure below, thread A holds resource 2 and thread B holds resource 1. They both want to claim resources from each other at the same time, so the two threads wait for each other and enter A deadlock state.

Here is an example of thread deadlocks, code from the beauty of concurrent programming.

public class DeadLockDemo {
    private static Object resource1 = new Object();1 / / resources
    private static Object resource2 = new Object();2 / / resources

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (resource1) {
                System.out.println(Thread.currentThread() + "get resource1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resource2");
                synchronized (resource2) {
                    System.out.println(Thread.currentThread() + "get resource2"); }}},Thread 1 "").start();

        new Thread(() -> {
            synchronized (resource2) {
                System.out.println(Thread.currentThread() + "get resource2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resource1");
                synchronized (resource1) {
                    System.out.println(Thread.currentThread() + "get resource1"); }}},Thread 2 "").start(); }}Copy the code

The code output is as follows:

The Thread (Thread1.5,main]get resource1 Thread[Thread2.5,main]get resource2 Thread[Thread1.5,main]waiting get resource2 Thread2.5,main]waiting get resource1
Copy the code

Thread A acquires the monitor lock of resource1 through synchronized (resource1) and thread.sleep (1000); Let thread A sleep for 1s so that thread B can execute and then acquire the monitor lock of Resource2. When thread A and thread B sleep, they both attempt to request resources from each other, and then the two threads fall into A state of waiting for each other, resulting in A deadlock.

How do thread deadlocks occur? How to avoid it?

There are four necessary conditions for a deadlock to occur:

  • Mutually exclusive: a resource can only be used by one process at a time (resource independence)

  • Request and Hold: when a process is blocked by requesting a resource, it holds the acquired resource (without releasing the lock)

  • Undeprivation: A resource acquired by a process cannot be forcibly taken (robbed) before it is used.

  • Circular wait: A resource shutdown (an infinite loop) in which several processes wait head to tail in a circular manner

Ways to avoid deadlocks:

  • The first condition, “mutual exclusion”, cannot be broken, because the purpose of locking is to ensure mutual exclusion
  • Break the “hold and wait” condition by requesting all resources at once
  • If a thread that occupies a part of a resource fails to apply for other resources, it will release the occupied resource and break the “not preempt” condition
  • Request resources sequentially, breaking the “loop wait” condition

What is the difference between a run and a start thread?

The start() method is called to start a thread, and run() is automatically called when it is the thread’s turn to execute. A direct call to the run() method does not achieve the purpose of starting multithreading. It is equivalent to the main Thread executing the run() method of Thread object linearly. A thread of line start () method can only be called once, multiple calls will be thrown. Java lang. IllegalThreadStateException exception; The run() method has no limits.

What methods do threads have?

join

Thread.join() creates Thread in main and calls thread.join()/thread.join(long millis). The thread is WAITING/TIMED_WAITING and waits until the thread is finished executing the main thread.

public final void join(a) throws InterruptedException {
    join(0);
}
Copy the code

yield

Thread.yield(), which must be called by the current Thread. The current Thread gives up the CPU slice, but does not release the lock resource. The running state changes to the ready state, allowing the OS to select the Thread again. What it does: Allows threads of the same priority to take turns, but there is no guarantee that they will take turns. In practice, there is no guarantee that yield() will yield because the yielding thread may be selected again by the thread scheduler. Thread.yield() does not block. This method is similar to sleep(), except that the user cannot specify how long to pause.

public static native void yield(a); / / static method
Copy the code

sleep

Thread.sleep(long millis), the current Thread enters the TIMED_WAITING state, releases the CPU resource, does not release the object lock, and resumes running when the specified time is up. Action: The best way to give other threads a chance to execute.

public static native void sleep(long millis) throws InterruptedException;/ / static method
Copy the code

Underlying principle of volatile

Volatile is a lightweight synchronization mechanism that guarantees the visibility of variables to all threads, not atomicity.

  1. When a volatile variable is written, the JVM sends an instruction prefixed with LOCK to the processor to write the data from the cached row of that variable back to system memory.
  2. Due to the cache coherence protocol, each processor by sniffing the spread of the data on the bus to check their cache is expired, when the processor found himself cache line corresponding to the memory address has been changed, and will replace the current processor cache line for invalid state, when the processor to modify the data operation, Data is re-read from system memory into the processor cache.

MESI (Cache Consistency Protocol) : When a CPU writes data, if it finds that the variable being operated on is a shared variable, that is, a copy of the variable exists in other cpus, a signal is sent to the other cpus to invalidate the cache row of the variable, so that when other cpus need to read the variable, they re-read it from memory.

The volatile keyword serves two purposes:

  1. This ensures visibility when different threads operate on shared variables, i.e. when one thread changes the value of a variable, the new value is immediately visible to other threads.
  2. Command reordering is disabled.

Instruction reordering is a process by which the JVM optimizes instructions and improves program efficiency by maximizing parallelism without affecting the execution results of a single-threaded program. The Java compiler inserts a memory barrier instruction in place to disallow processor reordering when the instruction family is generated. By inserting a memory barrier, you tell the CPU and compiler that the command must be executed first and the command must be executed later. Write to a volatile field, and the Java memory model inserts a write barrier instruction after the write, which flusher all previously written values to memory.

AQS principle

AQS, AbstractQueuedSynchronizer, abstract queue synchronizer, defines a set of synchronizer framework for multithreaded access to a Shared resource, the realization of many concurrent tools are dependent on it, such as commonly used already/Semaphore/CountDownLatch.

AQS uses a volatile int member variable state to indicate synchronization status. CAS modifies the synchronization status. When a thread calls the lock method, if state=0, no thread holds the lock on the shared resource, the lock can be acquired and state=1. If state=1, a thread is currently using a shared variable, and other threads must join the synchronization queue to wait.

private volatile int state;// Share variables with volatile modifier to ensure thread visibility
Copy the code

Synchronizer rely on internal synchronous queue (two-way a FIFO queue) to complete synchronization state management, the current thread for synchronous state failure, synchronizer will the current thread and wait states (exclusive or Shared) structure become a Node (the Node) and add it to the synchronous queue and spin, when sync release, Wakes up the thread corresponding to the subsequent node in the first section to try again to get the synchronization status.

What are the uses of synchronized?

  1. Modifies ordinary methods that apply to the current object instance and acquire the lock of the current object instance before entering the synchronization code
  2. Static methods: apply to the current class and acquire the lock of the current class object before entering the synchronized code. Synchronized (class
  3. Modify code block: specifies the lock object, locks the given object, and obtains the lock for the given object before entering the synchronous code base

What does Synchronized do?

Atomicity: access synchronization code that ensures threads are mutually exclusive; Visibility: Changes to a shared variable must be visible in the main memory before an unlock operation is performed. If you lock a variable, the working memory of the variable will be empty. Before the execution engine can use the variable, you need to load or assign the variable value from the main memory again. Order: Solve the reordering problem effectively, i.e. an unlock operation happens before another lock operation on the same lock.

The underlying implementation principle of synchronized?

Synchronized code blocks are implemented through monitorenter and Monitorexit directives, where monitorenter points to the start of the synchronized code block and Monitorexit specifies the end of the synchronized code block. When monitorenter is executed, the thread attempts to acquire the lock, that is, the monitor. (The monitor object exists in the object header of every Java object, and synchronized locks are acquired this way. This is why any object in Java can be used as a lock.

Its internal contains a counter, when the counter is 0 can be successfully obtained, after obtaining the lock counter set to 1, that is, add 1. Accordingly, after monitorexit, the lock counter is set to 0 to indicate that the lock is released. If the object lock fails to be acquired, the current thread blocks and waits until the lock is released by another thread

Instead of monitorenter and Monitorexit, synchronized does replace the monitorenter with the ACC_SYNCHRONIZED flag, which identifies the method as a synchronized method. The JVM uses the ACC_SYNCHRONIZED access flag to tell if a method is declared to be a synchronized method and to perform the corresponding synchronized call.

How does ReentrantLock achieve reentrancy?

ReentrantLock internally defines Sync, which uses CAS algorithm to place thread objects in a bidirectional linked list. Each time the lock is obtained, check whether the ID of the thread currently maintained is consistent with the ID of the thread currently requested. If the ID is consistent, the synchronization status will be increased by 1. Indicates that the lock has been acquired multiple times by the current thread.

ReentrantLock is different from synchronized

  1. Synchronized is used to automatically release the lock after a thread executes the synchronized code block. ReentrantLock manually releases the lock.
  2. Synchronized is a non-fair lock. ReentrantLock can be set to a fair lock.
  3. Threads waiting to acquire a lock on ReentrantLock are interruptible and can waive the wait. And synchonized will wait indefinitely.
  4. ReentrantLock can set timeout to acquire the lock. Retrieves the lock before the specified deadline, or returns if the lock has not been acquired by the deadline.
  5. The tryLock() method of ReentrantLock can attempt a non-blocking lock acquisition and return immediately after calling it, returning true if it can get it and false otherwise.

The difference between wait() and sleep()

Similarities:

  1. Suspends the current thread, passing the opportunity to another thread
  2. Any thread that is interrupted while waiting throws InterruptedException

Difference:

  1. Wait () is a method in the Object superclass; Sleep () is a method in the Thread class
  2. Locks are held differently; wait() releases the lock, while sleep() does not
  3. Wait () relies on notify or notifyAll, interruption, and reaching the specified time to wake up. And sleep() wakes up at the specified time
  4. Calling obj.wait() requires acquiring the lock of the object, while thread.sleep () does not

The difference between wait(),notify() and suspend(),resume()

  • Wait () causes the thread to enter the blocking wait state and releases the lock
  • Notify () wakes up a thread in the wait state and is usually used with wait ().
  • Suspend () puts a thread into a blocked state and does not resume automatically. Resume () must be called before the thread can be put back into an executable state. The suspend() method can easily cause deadlock problems.
  • The resume() method is used with the suspend() method.

Suspend () is not recommended. After the suspend() method is invoked, the thread does not release assets (such as locks) that it has held. Instead, it holds resources and goes to sleep, which may cause deadlocks.

What’s the difference between Runnable and Callable?

  • The Callable interface method is call() and the Runnable method is run();
  • The Call method of the Callable interface has a return value and supports generics. The Run method of the Runnable interface has no return value.
  • The Call () method of the Callable interface allows exceptions to be thrown; The Runnable interface run() method cannot continue to throw exceptions;

What is the difference between volatile and synchronized?

  1. Volatile can only be used on variables; Synchronized can be found on classes, variables, methods, and code blocks.
  2. Volatile to ensure visibility; Synchronized guarantees atomicity and visibility.
  3. Volatile disables instruction reordering; Synchronized.
  4. Volatile does not block; Synchronized.

How to control the order of thread execution?

Given that there are three threads, T1, T2, and T3, how do you ensure that T2 is executed after T1 and T3 after T2?

You can solve this problem using the Join method. For example, in thread A, calling thread B’s join method means ** : THREAD A waits for thread B to finish (freeing CPU execution) before continuing to execute. 支那

The code is as follows:

public class ThreadTest { public static void main(String[] args) { Thread spring = new Thread(new SeasonThreadTask (" spring ")); Thread summer = new Thread(new SeasonThreadTask); Thread autumn = new Thread(new SeasonThreadTask); Try {// Start spring.start(); // The main thread waits for spring to finish executing, then spring.join(); // Start the thread summer.start(); Summer.join (); // Wait for thread summer to finish executing, then execute summer.join(); // The thread starts autumn. Start (); // The main thread waits for the thread autumn to complete, and then executes autumn.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } class SeasonThreadTask implements Runnable{ private String name; public SeasonThreadTask(String name){ this.name = name; } @Override public void run() { for (int i = 1; i <4; I++) {System. Out. Println (enclosing the name + ":" + I + ""); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }}}}Copy the code

Running results:

Spring has come: 1 Spring has come: 2 Spring has come: 3 Summer has Come: 1 Summer has Come: 2 Summer has Come: 3 Fall has come: 1 Fall has come: 2 Fall has come: 3Copy the code

Is optimism lock necessarily good?

Optimistic locking avoids the phenomenon of pessimistic locking monopolizing objects and improves concurrency performance, but it also has disadvantages:

  • Optimistic locking can only guarantee atomic operations on a shared variable. Optimistic locking becomes unmanageable if one or more variables are added, but mutex can be easily solved regardless of the number of objects and their granularity.
  • Spinning for long periods of time can result in high overhead. If the CAS spins unsuccessfully for a long period of time, it incurs a lot of CPU overhead.
  • ABA problem. The core of the CAS is through comparing memory value and expected values are the same Broken memory value will be changed, but the judgment logic is not strict, if memory value turns out to be A, was A thread to B, then be changed to A, the CAS think this memory value did not change, but in fact is by other threads to mend, This situation has a significant impact on the results of operations in scenarios that depend on process values. The idea is to introduce a version number, incrementing the version number each time a variable is updated.

What is a daemon thread?

A daemon thread is a special process that runs in the background. It is independent of the control terminal and periodically performs some task or waits for some event to occur. In Java, garbage collection threads are special daemon threads.

Communication between threads

volatile

Volatile is a lightweight synchronization mechanism that guarantees the visibility of variables to all threads, not atomicity.

synchronized

Ensure visibility and exclusivity of thread access to variables.

Wait notification mechanism

Wait /notify is the method of an Object. To invoke wait/notify, obtain the lock of the Object first. When the notify thread calls the notify() method of the object, the waiting thread does not return from the wait immediately. Instead, it waits for the notifying thread to release the lock and wait for the thread in the wait queue to acquire the lock. Return from wait() only if the thread has acquired the lock.

The wait notification mechanism relies on the synchronization mechanism to ensure that the notification thread is aware of changes to the variable value of an object when it returns from the WAIT method.

ThreadLocal

Thread-local variables. When using ThreadLocal to maintain variables, ThreadLocal provides a separate copy of the variable for each thread that uses the variable, so each thread can independently change its copy without affecting other threads.

ThreadLocal principle

Each thread has a ThreadLocalMap(ThreadLocal inner class) in which the key of the element is ThreadLocal and the value corresponds to a copy of the variable of the thread.

Call threadLocal.set()–> call getMap(Thread)–> return ThreadLocalMap< threadLocal, value>–>map.set(this, value), This is a ThreadLocal

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if(map ! =null)
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Copy the code

Get ()–> call getMap(Thread)–> return ThreadLocalMap<ThreadLocal, value>–> map.getentry (this), return value

    public T get(a) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if(map ! =null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if(e ! =null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                returnresult; }}return setInitialValue();
    }
Copy the code

The key for a ThreadLocalMap of type threadLocals is a ThreadLocal object, since there can be multiple ThreadLocal variables per thread, such as longLocal and stringLocal.

public class ThreadLocalDemo { ThreadLocal<Long> longLocal = new ThreadLocal<>(); public void set() { longLocal.set(Thread.currentThread().getId()); } public Long get() { return longLocal.get(); } public static void main(String[] args) throws InterruptedException { ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo(); threadLocalDemo.set(); System.out.println(threadLocalDemo.get()); Thread thread = new Thread(() -> { threadLocalDemo.set(); System.out.println(threadLocalDemo.get()); }); thread.start(); thread.join(); System.out.println(threadLocalDemo.get()); }}Copy the code

ThreadLocal is not designed to solve the problem of multi-threaded access to shared resources because the resources in each thread are copies and not shared. ThreadLocal is therefore suitable as a thread context variable to simplify in-thread parameter passing.

The cause of the ThreadLocal memory leak?

Each Thread has an internal attribute of ThreadLocalMap. The map’s key is ThreaLocal, which is defined as a weak reference, and its value is a strong reference type. While the key is automatically reclaimed during GC, the value is reclaimed depending on the lifetime of the Thread object. Thread objects are often reused in a Thread pool to save resources, which results in a long lifetime of Thread objects, so there is always a strong chain of references: Thread –>Entry–>Value. As the task is executed, the number of ThreadLocalMap–>Entry–>Value may increase and cannot be released, resulting in memory leaks.

Workaround: Call ThreadLocal’s remove() method every time you’re done using it and manually remove the corresponding key-value pair to avoid memory leaks.

currentTime.set(System.currentTimeMillis());
result = joinPoint.proceed();
Log log = new Log("INFO",System.currentTimeMillis() - currentTime.get());
currentTime.remove();
Copy the code

What are the scenarios for using ThreadLocal?

When ThreadLocal is used, each thread needs to have its own instance, and it needs to be shared among multiple methods, that is, it needs to be isolated between threads and shared between methods. For example, in Java Web applications where each thread has its own Session instance, ThreadLocal can be used.

The classification of the lock

Fair and unfair locks

Object locks are acquired in thread access order. Synchronized is a non-fair Lock. Lock is a non-fair Lock by default. You can set it to a fair Lock, which affects performance.

public ReentrantLock(a) {
    sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
Copy the code

Shared and exclusive locks

The main difference between the shared mode and the exclusive mode is that only one thread can acquire the synchronization state at the same time, while the shared mode can have multiple threads acquire the synchronization state at the same time. For example, read operations can be performed by multiple threads at the same time, while write operations can be performed by only one thread at a time. All other operations will be blocked.

Pessimistic locks and optimistic locks

Pessimistic locks are locked every time a resource is accessed and released after the execution of the synchronization code. Synchronized and ReentrantLock are pessimistic locks.

Optimistic lock, does not lock the resource, all threads can access and modify the same resource, if there is no conflict, the modification is successful and exit, otherwise the loop will continue trying. The most common implementation of optimistic locking is CAS.

Optimistic locking generally has the following two ways:

  1. It is implemented using the data version logging mechanism, which is the most common implementation of optimistic locking. Adding a version identifier to data is typically done by adding a numeric version field to a database table. When the data is read, the value of the Version field is read together, incrementing the version value with each update of the data. When we submit the update, compare the current version information of the database table with the version value extracted for the first time. If the current version number of the database table is equal to the version value extracted for the first time, it will be updated; otherwise, it is considered as expired data.
  2. Use a timestamp. Add a field to the database table, and the field type is timestamp (timestamp), similar to the above version, also check the timestamp of the data in the current database when submitting the update and compare it with the timestamp obtained before the update, if the same is OK, otherwise it is version conflict.

Applicable scenarios:

  • Pessimistic locking is suitable for scenarios with many write operations.
  • Optimistic lock is suitable for scenarios where many read operations are performed. No lock can improve read operation performance.

CAS

What is CAS?

CAS stands for Compare And Swap, which is the main implementation method of optimistic lock. CAS implements variable synchronization between multiple threads without using locks. CAS is used within AQS within ReentrantLock and within atomic classes.

The CAS algorithm involves three operands:

  • Memory value V that needs to be read or written.
  • The value A for comparison.
  • The new value B to write.

The value of V is updated atomically with the new value B only if the value of V is equal to A, otherwise retry until the value is successfully updated.

Take AtomicInteger as an example, the bottom layer of getAndIncrement() method of AtomicInteger is CAS implementation, the key code is compareAndSwapInt(obj, offset, expect, Update), The implication is that if the value in obj is equal to expect, then no other thread has changed the variable, so update it to update it, and if not, retry until the value is successfully updated.

Problems with CAS?

Three major issues concerning CAS:

  1. ABA problem. The CAS checks whether the memory value is changed when operating on the value. If the memory value is not changed, the CAS updates the memory value. But if the memory value is A, then B, and then A again, CAS checks to see that the value has not changed, but it has. The solution to the ABA problem is to add A version number to the variable, adding the version number by one each time the variable is updated, and changing the process from A-B-A to 1A-2B-3A.

    The JDK has provided an AtomicStampedReference class to address ABA issues since 1.5, where atoms update reference types with version numbers.

  2. Long cycle time and high overhead. If the CAS operation is not successful for a long time, the CAS operation spins all the time, which incurs high CPU overhead.

  3. Atomic operations of only one shared variable are guaranteed. CAS guarantees atomic operations on one shared variable, but cannot guarantee atomic operations on multiple shared variables.

    The JDK provides the AtomicReference class to ensure atomicity between reference objects. Multiple variables can be placed in one object for CAS operations.

Concurrent tool

Several very useful concurrency utility classes are provided in the JDK parallel distribution package. The CountDownLatch, CyclicBarrier, and Semaphore utility classes provide a means of controlling concurrent processes.

CountDownLatch

CountDownLatch is used by a thread to wait for other threads to complete their tasks before executing, similar to the thread.join() function. A common application scenario is to start multiple threads to execute a task at the same time and wait until all tasks are complete before performing specific operations, such as summarizing statistics.

public class CountDownLatchDemo { static final int N = 4; static CountDownLatch latch = new CountDownLatch(N); public static void main(String[] args) throws InterruptedException { for(int i = 0; i < N; i++) { new Thread(new Thread1()).start(); } latch.await(1000, TimeUnit.MILLISECONDS); // The thread calling the await() method is suspended and waits until count is 0 before continuing; System.out.println(" Task Finished ") is executed if count is not 0 after timeout. } static class Thread1 implements Runnable { @Override public void run() { try { System.out.println(Thread.currentThread().getName() + "starts working"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { latch.countDown(); }}}}Copy the code

Running results:

Thread-0starts working
Thread-1starts working
Thread-2starts working
Thread-3starts working
task finished
Copy the code

CyclicBarrier

CyclicBarrier, a group of threads that wait for each other to reach a certain state before executing simultaneously.

public CyclicBarrier(int parties, Runnable barrierAction) {}public CyclicBarrier(int parties) {}Copy the code

The parties parameter specifies how many threads or tasks to wait until a state is reached. The barrierAction parameter is what will be executed when these threads have all reached a certain state.

public class CyclicBarrierTest {
    // The number of requests
    private static final int threadCount = 10;
    // The number of threads to synchronize
    private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(5);

    public static void main(String[] args) throws InterruptedException {
        // Create a thread pool
        ExecutorService threadPool = Executors.newFixedThreadPool(10);

        for (int i = 0; i < threadCount; i++) {
            final int threadNum = i;
            Thread.sleep(1000);
            threadPool.execute(() -> {
                try {
                    test(threadNum);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    // TODO Auto-generated catch blocke.printStackTrace(); }}); } threadPool.shutdown(); }public static void test(int threadnum) throws InterruptedException, BrokenBarrierException {
        System.out.println("threadnum:" + threadnum + "is ready");
        try {
            /** Wait 60 seconds to ensure that the child thread has completed execution */
            cyclicBarrier.await(60, TimeUnit.SECONDS);
        } catch (Exception e) {
            System.out.println("-----CyclicBarrierException------");
        }
        System.out.println("threadnum:" + threadnum + "is finish"); }}Copy the code

The CyclicBarrier is reusable:

threadnum:0is ready threadnum:1is ready threadnum:2is ready threadnum:3is ready threadnum:4is ready threadnum:4is finish  threadnum:3is finish threadnum:2is finish threadnum:1is finish threadnum:0is finish threadnum:5is ready threadnum:6is ready ...Copy the code

When all four threads reach the Barrier state, one of the four threads is selected to execute the Runnable.

CyclicBarrier is different from CountDownLatch

CyclicBarrier and CountDownLatch both enable waiting between threads.

CountDownLatch is used by a thread to wait for another thread to complete its task before executing. CyclicBarrier is used when a group of threads wait for each other to reach a certain state and then execute simultaneously. CountDownLatch’s counter can only be used once, while CyclicBarrier’s counter can be reset using the reset() method and can be used for more complex business scenarios.

Semaphore

Semaphore is a lock that controls the number of threads accessing a particular resource at the same time and controls the number of concurrent threads.

public class SemaphoreDemo { public static void main(String[] args) { final int N = 7; Semaphore s = new Semaphore(3); for(int i = 0; i < N; i++) { new Worker(s, i).start(); } } static class Worker extends Thread { private Semaphore s; private int num; public Worker(Semaphore s, int num) { this.s = s; this.num = num; } @Override public void run() { try { s.acquire(); System.out.println("worker" + num + " using the machine"); Thread.sleep(1000); System.out.println("worker" + num + " finished the task"); s.release(); } catch (InterruptedException e) { e.printStackTrace(); }}}}Copy the code

The result of the run is as follows. You can see that the locks of resources are not acquired in thread access order, i.e

worker0 using the machine worker1 using the machine worker2 using the machine worker2 finished the task worker0 finished  the task worker3 using the machine worker4 using the machine worker1 finished the task worker6 using the machine worker4 finished the task worker3 finished the task worker6 finished the task worker5 using the machine worker5 finished  the taskCopy the code

Atomic classes

Base type atomic class

Update base types atomically

  • AtomicInteger: Integer atomic class
  • AtomicLong: long integer atomic class
  • AtomicBoolean: Boolean atomic class

The AtomicInteger class uses the following methods:

public final int get(a) // Get the current value
public final int getAndSet(int newValue)// Get the current value and set the new value
public final int getAndIncrement(a)// Get the current value and increment it
public final int getAndDecrement(a) // Get the current value and decrement it
public final int getAndAdd(int delta) // Get the current value and add the expected value
boolean compareAndSet(int expect, int update) // If the input value is equal to the expected value, set it atomically to the input value (update)
public final void lazySet(int newValue)// Finally set to newValue. Using the lazySet setting may cause other threads to read the old value for a short time later.
Copy the code

AtomicInteger class mainly uses CAS (compare and swap) to ensure atomic operations, thus avoiding the high overhead of locking.

Array type atomic class

Update an element in an array atomically

  • AtomicIntegerArray: Integer array atomic class
  • AtomicLongArray: Long integer array atomic class
  • AtomicReferenceArray: Reference type array atomic class

AtomicIntegerArray class

public final int get(int i) // Get the value of the element at index= I
public final int getAndSet(int i, int newValue)// return the current value at index= I and set it to the newValue: newValue
public final int getAndIncrement(int i)// Get the value of the element at index= I and increment the element at that position
public final int getAndDecrement(int i) // Get the value of the element at index= I, and let the element at that position decrement
public final int getAndAdd(int i, int delta) // Get the value of the element at index= I and add the expected value
boolean compareAndSet(int i, int expect, int update) // If the input value is equal to the expected value, atomically set the element value at index= I to the input value (update)
public final void lazySet(int i, int newValue)// Finally set the element at index= I to newValue. Using the lazySet setting may cause other threads to read the old value for a short time afterwards.
Copy the code

Reference type atomic class

  • AtomicReference: Reference type atomic class
  • AtomicStampedReference: Atom class of a reference type with a version number. This class associates integer values with references and can be used to resolve atomic update data and the version number of the data, and can solve ABA problems that may occur when atomic updates are made using CAS.
  • AtomicMarkableReference: Atom updates reference types with tags. This class associates a Boolean tag with a reference

Github repository, which contains more than 200 classic computer books, including C language, C++, Java, Python, front-end, database, operating system, computer networks, data structures and algorithms, machine learning, programming life, etc., can be star, next time looking for books directly on the above search. The warehouse is being updated

Github address: github.com/Tyson0314/j…

If Github is not available, access the Gitee repository.

Gitee address: gitee.com/tysondai/ja…