Java concurrency
Hi everyone, this is Da Bin. In recent interviews, seen a lot of faces by the common Java Concurrency common interview questions summed up, if you have help, can collection and thumbs , follow-up will continue to update new interview questions Oh!
The article catalog is as follows:
First of all to share a github repository, put on top of 200 Duo classic computer books , including the C language, C ++, Java, Python, front-end, database, operating systems, computer networks, data structures and algorithms, machine learning, programming Life and so on, you can star, next time you find a book, search directly on it, and the warehouse is continuously updating~
github address: https://github.com/Tyson0314/java-books
If github is not accessible, you can visit the gitee repository.
gitee address: https://gitee.com/tysondai/java-books
Thread Pool
Thread pool: A pool for managing threads.
Why use thread pool?
- reduces resource consumption . Reduce the consumption caused by thread creation and destruction by reusing the created threads.
- improves response speed . When the task arrives, the task can be executed immediately without waiting until the thread is created.
- improves thread manageability . Unified management of threads, to avoid the system creating a large number of threads of the same type and causing the memory to run out.
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);
How does the thread pool execute?
To create a new thread, you need to acquire a global lock. Through this design, you can avoid acquiring a global lock as much as possible. After the ThreadPoolExecutor finishes warming up (the number of currently running threads is greater than or equal to corePoolSize), most of the submitted tasks will be placed in the BlockingQueue.
In order to vividly describe the thread pool execution, make an analogy:
- The core thread is compared to the company's full-time employees
- Non-core threads are compared to outsourced employees
- The blocking queue is compared to the demand pool
- Comparing the submission of tasks to the requirements
What are the thread pool parameters?
The general constructor of ThreadPoolExecutor:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);
- corePoolSize: When there is a new task, if the number of threads in the thread pool does not reach the basic size of the thread pool, a new thread will be created to perform the task, otherwise the task will be placed in the blocking queue. When the number of surviving threads in the thread pool is always greater than corePoolSize, you should consider increasing corePoolSize.
- maximumPoolSize: When the blocking queue is full, if the number of threads in the thread pool does not exceed the maximum number of threads, a new thread will be created to run the task. Otherwise, the new task will be processed according to the rejection policy. Non-core threads are similar to temporarily borrowed resources. These threads should exit after their idle time exceeds keepAliveTime to avoid waste of resources.
- BlockingQueue: Store tasks waiting to be run.
- keepAliveTime: non-core thread after idle, keep alive time, this parameter is only valid for non-core threads. Set to 0, which means that redundant idle threads will be terminated immediately.
TimeUnit: Time unit
TimeUnit.DAYS TimeUnit.HOURS TimeUnit.MINUTES TimeUnit.SECONDS TimeUnit.MILLISECONDS TimeUnit.MICROSECONDS TimeUnit.NANOSECONDS
ThreadFactory: Whenever a new thread is created in the thread pool, it is done through the thread factory method. Only one method newThread is defined in ThreadFactory, and it is called whenever the thread pool needs to create a new thread.
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);//将线程池名字传递给构造函数,用于区分不同线程池的线程 } }
RejectedExecutionHandler: When the queue and thread pool are full, new tasks are processed according to the rejection policy.
AbortPolicy:默认的策略,直接抛出RejectedExecutionException DiscardPolicy:不处理,直接丢弃 DiscardOldestPolicy:将等待队列队首的任务丢弃,并执行当前任务 CallerRunsPolicy:由调用线程处理该任务
How to set the thread pool size?
If the number of threads in the thread pool is too small, when there are a large number of requests to be processed, the slow response of the system affects the experience, and even a large number of tasks in the task queue may cause OOM.
If the number of threads in the thread pool is too large, a large number of threads may be fighting for CPU resources at the same time, which will lead to a large number of context switches (cpu allocates time slices to threads, and saves the state when the cpu time slices of the threads are used up, so that they can continue to run next time. ), thereby increasing the execution time of the thread and affecting the overall execution efficiency.
CPU-intensive tasks (N+1) : This task mainly consumes CPU resources. The number of threads can be set to N (the number of CPU cores)+1. One thread more than the number of CPU cores is to prevent certain The impact of task suspension caused by some reasons (thread blocking, such as io operation, waiting for lock, thread sleep). Once a thread is blocked, the cpu resources are released, and in this case an extra thread can make full use of the idle time of the CPU.
I/O intensive tasks (2N) : The system will spend most of the time processing I/O operations, and threads waiting for I/O operations will be blocked, releasing cpu resources, then the CPU can be handed over to Used by other threads. Therefore, in the application of I/O intensive tasks, we can configure more threads, the specific calculation method: optimal number of threads = number of CPU cores (1/CPU utilization) = number of CPU cores (1 + (I /O time-consuming/CPU time-consuming)), generally can be set to 2N
What are the types of thread pools? Applicable scene?
Common thread pools are FixedThreadPool, SingleThreadExecutor, CachedThreadPool and ScheduledThreadPool. These are all instances of ExecutorService (thread pool).
FixedThreadPool
A thread pool with a fixed number of threads. At any point in time, at most nThreads threads are active to perform tasks.
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
Using the unbounded queue LinkedBlockingQueue (queue capacity is Integer.MAX_VALUE), the running thread pool will not reject the task, that is, it will not call the RejectedExecutionHandler.rejectedExecution() method.
maxThreadPoolSize is an invalid parameter, so set its value to be consistent with coreThreadPoolSize.
keepAliveTime is also an invalid parameter, set to 0L, because all threads in this thread pool are core threads, and core threads will not be recycled (unless executor.allowCoreThreadTimeOut(true) is set).
Applicable scenarios: suitable for processing CPU-intensive tasks, ensuring that when the CPU is used by worker threads for a long time, as few threads are allocated as possible, that is, suitable for performing long-term tasks. It should be noted that FixedThreadPool will not reject tasks. will cause OOM when there are more tasks.
SingleThreadExecutor
A thread pool with only one thread.
public static ExecutionService newSingleThreadExecutor() {
return new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
Use an unbounded queue LinkedBlockingQueue. There is only one running thread in the thread pool, new tasks are put into the work queue, and the thread processes the tasks and then cyclically obtains tasks from the queue for execution. Ensure that each task is executed in order.
Applicable scenario: It is suitable for the scenario of serial execution of tasks, task by task. will also cause OOM when there are more tasks.
CachedThreadPool
Create a thread pool of new threads as needed.
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}
If the main thread submits tasks faster than the threads process tasks, CachedThreadPool
will continue to create new threads. In extreme cases, this will cause exhaustion of cpu and memory resources.
Use SynchronousQueue with no capacity as the thread pool work queue. When the thread pool has idle threads, SynchronousQueue.offer(Runnable task)
will be processed by the idle threads, otherwise a new thread will be created to process the tasks.
Applicable scenarios: Used for concurrent execution of a large number of short-term small tasks. CachedThreadPool
allows the number of threads to 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 basically not used in actual projects, because there are other options such as quartz
.
Task queue used DelayQueue
encapsulates a PriorityQueue
, PriorityQueue
will queue the task of sorting, the first time early tasks to be performed (ie ScheduledFutureTask
of time
variable small first execution), if the same time the first task will be submitted to Execute ( ScheduledFutureTask
variable of 0614a018fd3e2a of squenceNumber
executed first).
Steps to perform periodic tasks:
- Thread from
DelayQueue
get expired inScheduledFutureTask(DelayQueue.take())
. Due task means thatScheduledFutureTask
is greater than or equal to the time of the current system; - Execute this
ScheduledFutureTask
; - Modify
ScheduledFutureTask
to be the time to be executed next time; ScheduledFutureTask
after modifying the time back intoDelayQueue
(DelayQueue.add()
).
Applicable scenarios: scenarios where tasks are performed periodically, and scenarios where the number of threads needs to be limited.
Process thread
A process refers to an application program running in memory. Each process has its own independent piece of memory space, and multiple threads can be started in a process.
A thread is an execution unit smaller than a process. It is an independent control flow in a process. A process can start multiple threads, and each thread executes different tasks in parallel.
Thread life cycle
Initial (NEW): The thread is constructed and start() has not been called yet.
Run (RUNNABLE): Including the ready and running status of the operating system.
Blocked (BLOCKED): Generally passive, the resource cannot be obtained in the preemption of resources, passively hangs in the memory, and waits for the resource to be released to wake it up. The blocked thread will release the CPU, not the memory.
Waiting (WAITING): The thread entering this state needs to wait for other threads to make some specific actions (notification or interrupt).
TIMED_WAITING: This state is different from WAITING, it can return by itself after a specified time.
Termination (TERMINATED): Indicates that the thread has been executed.
Image Source: The Art of Concurrent Programming in Java
Talk about thread interruption?
Thread interruption means that the thread is interrupted by other threads during its operation. The biggest difference between it and stop is: stop is the system forcibly terminating the thread, while thread interruption is to send an interrupt signal to the target thread. If the target thread does not receive the thread The interrupt signal and end the thread, the thread will not terminate, the specific exit or execution of other logic depends on the target thread.
Three important methods of thread interruption:
1、java.lang.Thread#interrupt
The interrupt() method of the target thread is called to send an interrupt signal to the target thread, and the thread is marked as interrupted.
2、java.lang.Thread#isInterrupted()
To determine whether the target thread is interrupted, the interrupt flag will not be cleared.
3、java.lang.Thread#interrupted
To determine whether the target thread is interrupted, the interrupt flag will be cleared.
private static void test2() {
Thread thread = new Thread(() -> {
while (true) {
Thread.yield();
// 响应中断
if (Thread.currentThread().isInterrupted()) {
System.out.println("Java技术栈线程被中断,程序退出。");
return;
}
}
});
thread.start();
thread.interrupt();
}
What are the ways to create threads?
- Create multiple threads by extending the Thread class
- By implementing the Runnable interface to create multiple threads, resource sharing between threads can be realized
- Implement the Callable interface and create a thread through the FutureTask interface.
- Use the Executor framework to create a thread pool.
inherits Thread to create thread code is as follows. The run() method is a callback method after the operating system level thread is created by the jvm. It cannot be called manually. Manual calling is equivalent to calling a common method.
/**
* @author: 程序员大彬
* @time: 2021-09-11 10:15
*/
public class MyThread extends Thread {
public MyThread() {
}
@Override
public void run() {
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 = new MyThread();
mThread1.start();
mThread2.start();
myThread3.start();
}
}
Runnable creates thread code :
/**
* @author: 程序员大彬
* @time: 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("主线程:["+Thread.currentThread().getName()+"]");
}
}
class Runnable1 implements Runnable{
@Override
public void run() {
System.out.println("当前线程:"+Thread.currentThread().getName());
}
}
The advantages of implementing the Runnable interface over inheriting the Thread class:
- Resource sharing, suitable for multiple threads of the same program code to process the same resource
- Can avoid the limitation of single inheritance in java
- The thread pool can only be placed in threads that implement Runable or Callable classes, and cannot be directly placed in classes that inherit Thread
Callable Create thread code :
/**
* @author: 程序员大彬
* @time: 2021-09-11 10:21
*/
public class CallableTest {
public static void main(String[] args) {
Callable1 c = new Callable1();
//异步计算的结果
FutureTask<Integer> result = new FutureTask<>(c);
new Thread(result).start();
try {
//等待任务完成,返回结果
int sum = result.get();
System.out.println(sum);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
class Callable1 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
return sum;
}
}
uses Executor to create thread code :
/**
* @author: 程序员大彬
* @time: 2021-09-11 10:44
*/
public class ExecutorsTest {
public static void main(String[] args) {
//获取ExecutorService实例,生产禁用,需要手动创建线程池
ExecutorService executorService = Executors.newCachedThreadPool();
//提交任务
executorService.submit(new RunnableDemo());
}
}
class RunnableDemo implements Runnable {
@Override
public void run() {
System.out.println("大彬");
}
}
What is thread deadlock?
Multiple threads are blocked at the same time, one or all of them are waiting for a resource to be released. Because the thread is blocked indefinitely, it is impossible for the program to terminate normally.
As shown in the figure below, thread A holds resource 2 and thread B holds resource 1. They both want to apply for each other's resources at the same time, so these two threads will wait for each other and enter a deadlock state.
The following example illustrates thread deadlock, the code comes from the beauty of concurrent programming.
public class DeadLockDemo {
private static Object resource1 = new Object();//资源 1
private static Object resource2 = new Object();//资源 2
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");
}
}
}, "线程 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");
}
}
}, "线程 2").start();
}
}
The code output is as follows:
Thread[线程 1,5,main]get resource1
Thread[线程 2,5,main]get resource2
Thread[线程 1,5,main]waiting get resource2
Thread[线程 2,5,main]waiting get resource1
Thread A obtains the monitor lock of resource1 through synchronized (resource1), and then uses Thread.sleep(1000); to let thread A sleep for 1s in order to allow thread B to be executed and then obtain the monitor lock of resource2. Thread A and Thread B both begin to request resources from each other after they have finished sleeping, and then the two threads will fall into a state of waiting for each other, which also produces a deadlock.
How does thread deadlock occur? How to avoid it?
four necessary conditions for deadlock arising :
- Mutually exclusive: a resource can only be used by one process at a time (resource independent)
- Request and hold: When a process is blocked by requesting resources, it keeps on holding the acquired resources (do not release the lock)
- No deprivation: the resources already acquired by the process cannot be deprived forcibly (grabbing resources) before they are used
- Cyclic waiting: a loop waiting for resource closure (end-to-end loop) is formed between several processes
Ways to avoid deadlock:
- The first condition "mutual exclusion" cannot be broken, because locking is to ensure mutual exclusion
- Apply for all resources at once, destroy the "possess and wait" condition
- When the thread occupying part of the resource further applies for other resources, if it fails to apply, it will actively release the resources it occupies, destroying the "non-preemptable" condition
- Apply for resources in order, destroy the "loop waiting" condition
The difference between thread run and start?
Calling the start() method is used to start the thread. When it is the thread's turn to execute, run() is automatically called; calling the run() method directly cannot achieve the purpose of starting multithreading, which is equivalent to the linear execution of the Thread object by the main thread run() method.
The start() method of a thread can only be called once, and multiple calls will throw java.lang.IllegalThreadStateException; the run() method has no restrictions.
What are the methods for threads?
join
Thread.join(), thread thread is created in main, thread.join()/thread.join(long millis) is called in main, main thread gives up cpu control, thread enters WAITING/TIMED_WAITING state, wait until thread thread The execution of the main thread is continued after execution.
public final void join() throws InterruptedException {
join(0);
}
yield
Thread.yield(), this method must be called by the current thread. The current thread abandons the acquired CPU time slice, but does not release the lock resource, and changes from the running state to the ready state, allowing the OS to select the thread again. Function: Let threads of the same priority execute in turn, but there is no guarantee that they will execute in turn. In practice, there is no guarantee that yield() will achieve the concession purpose, because the concession thread may be selected again by the thread scheduler. Thread.yield() will not cause blocking. This method is similar to sleep(), except that the user cannot specify the length of the pause.
public static native void yield(); //static方法
sleep
Thread.sleep(long millis), this method must be called by the current thread, and the current thread enters the TIMED_WAITING state, giving up cpu resources, but does not release the object lock, and resumes running after the specified time. Role: The best way to give other threads a chance to execute.
public static native void sleep(long millis) throws InterruptedException;//static方法
The underlying principle of volatile
Volatile is a lightweight synchronization mechanism. Volatile guarantees the visibility of variables to all threads and does not guarantee atomicity.
- When a volatile variable is written, the JVM will send a LOCK-prefixed instruction to the processor to write the data of the cache line where the variable is located back to the system memory.
- Due to the cache coherency protocol, each processor checks whether its own cache is expired by sniffing the data transmitted on the bus. When the processor finds that the memory address corresponding to its cache line has been modified, it will change the current processor’s The cache line is set to an invalid state. When the processor modifies this data, it will read the data from the system memory to the processor cache again.
MESI (Cache Consistency Protocol): When the CPU writes data, if the operating variable is found to be a shared variable, that is, a copy of the variable exists in other CPUs, it will send a signal to notify other CPUs to invalidate the cache line of the variable State, so when other CPUs need to read this variable, they will re-read it from memory.
Two functions of the volatile keyword:
- The visibility of operations on shared variables by different threads is guaranteed, that is, if one thread modifies the value of a variable, the new value is immediately visible to other threads.
- Reordering of instructions is prohibited.
Instruction reordering is the JVM in order to optimize the instructions and improve the efficiency of the program, so as to increase the degree of parallelism as much as possible without affecting the execution results of the single-threaded program. memory barrier instruction at the appropriate position when generating the instruction series to prohibit the processor from reordering. Inserting a memory barrier is equivalent to telling the CPU and the compiler that the commands that precede this command must be executed first, and those that follow this command must be executed later. For a write operation to a volatile field, the Java memory model will insert a write barrier instruction after the write operation. This instruction will flush all the previously written values to the memory.
AQS principle
AQS, AbstractQueuedSynchronizer, abstract queue synchronizer, defines a synchronizer framework for multi-threaded access to shared resources. The implementation of many concurrency tools depends on it, such as the commonly used ReentrantLock/Semaphore/CountDownLatch.
AQS uses a volatile int type member variable state to represent the synchronization state, and modifies the value of the synchronization state through CAS. When the thread calls the lock method, if state=0, it means that no thread holds the lock of the shared resource, and the lock can be obtained and state=1. If state=1, it means that a thread is currently using the shared variable, and other threads must join the synchronization queue to wait.
private volatile int state;//共享变量,使用volatile修饰保证线程可见性
The synchronizer relies on the internal synchronization queue (a FIFO two-way queue) to complete the management of the synchronization state. When the current thread fails to obtain the synchronization state, the synchronizer will construct the current thread and the waiting state (exclusive or shared) into a node (Node) and Add it to the synchronization queue and spin it. When the synchronization state is released, the thread corresponding to the successor node in the first section will be awakened to make it try to obtain the synchronization state again.
What are the uses of synchronized?
- Modification common method: acting on the current object instance, get the lock of the current object instance before entering the synchronization code
- Modified static method: Acting on the current class, you must obtain the lock of the current class object before entering the synchronization code. The synchronized keyword added to the static static method and synchronized(class) code block is to lock the Class class
- Modified code block: Specify the lock object, lock the given object, and obtain the lock of the given object before entering the synchronization code base
What are the functions of Synchronized?
Atomicity: to ensure that threads mutually exclusive access synchronization code;
Visibility: Ensure that the modification of shared variables can be seen in time. In fact, it is through the Java memory model "Before unlocking a variable, it must be synchronized to the main memory; if a variable is locked, the working memory will be cleared. Before the execution engine uses this variable, the value of this variable in the main memory needs to be re-initialized from the load operation or assign operation in the main memory" to ensure;
Orderliness: Effectively solve the problem of reordering, that is, "an unlock operation happens-before before subsequent lock operations on the same lock".
The underlying realization principle of synchronized?
The synchronized synchronization code block is realized by the monitorenter and monitorexit instructions, where the monitorenter instruction points to the start position of the synchronization code block, and the monitorexit instruction indicates the end position of the synchronization code block. When the monitorenter instruction is executed, the thread tries to acquire the lock, that is, to acquire the monitor (the monitor object exists in the object header of each Java object, and the synchronized lock acquires the lock in this way, which is why any object in Java can be used as a lock Reason).
It contains a counter inside. When the counter is 0, it can be successfully acquired. After the acquisition, the lock counter is set to 1, which is to increase by 1. Correspondingly, after the monitorexit instruction is executed, the lock counter is set to 0
, Indicating that the lock is released. If acquiring the object lock fails, the current thread will block and wait until the lock is released by another thread
The synchronized modified method does not have the monitorenter instruction and the monitorexit instruction. Instead, it is indeed the ACC_SYNCHRONIZED flag, which indicates that the method is a synchronized method. The JVM uses the ACC_SYNCHRONIZED access flag to identify whether a method is declared as a synchronized method and executes it. The corresponding synchronous call.
How does ReentrantLock achieve reentrancy?
ReentrantLock internally customizes the synchronizer Sync. When locking, it uses the CAS algorithm to put the thread object in a doubly linked list. Each time the lock is acquired, it is checked whether the currently maintained thread ID is consistent with the currently requested thread ID. If they are the same, the synchronization status is incremented by 1, indicating that the lock has been acquired by the current thread multiple times.
The difference between ReentrantLock and synchronized
- Use the synchronized keyword to achieve synchronization, the thread will automatically release the lock after executing the synchronized code block, and ReentrantLock needs to manually release the lock.
- Synchronized is a non-fair lock, ReentrantLock can be set to a fair lock.
- The thread waiting to acquire the lock on ReentrantLock is interruptible, and the thread can give up waiting for the lock. And synchonized will wait indefinitely.
- ReentrantLock can set a timeout to acquire the lock. Acquire the lock before the specified deadline, and return if the lock has not been acquired after the deadline.
- The tryLock() method of ReentrantLock can try to acquire the lock non-blockingly. It returns immediately after calling this method. If it can be acquired, it returns true, otherwise it returns false.
The difference between wait() and sleep()
Same point:
- Suspend the current thread and pass the opportunity to other threads
- Any thread interrupted while waiting will throw InterruptedException
difference:
- wait() is a method in the Object superclass; sleep() is a method in the Thread class
- The holding of the lock is different, wait() will release the lock, while sleep() does not release the lock
- The wake-up methods are not exactly the same, wait() relies on notify or notifyAll, interrupt, and reaches the specified time to wake up; while sleep() is waked up when the specified time is reached
- Calling obj.wait() needs to acquire the lock of the object first, while Thread.sleep() does not
The difference between wait(), notify() and suspend(), resume()
- wait() makes the thread enter the blocking waiting state and release the lock
- notify() wakes up a thread in a waiting state, it is generally used in conjunction with the wait() method.
- Suspend() makes the thread enter the blocked state and will not automatically resume. The corresponding resume() must be called to make the thread reenter the executable state. The suspend() method can easily cause deadlock problems.
- The resume() method is used in conjunction with the suspend() method.
suspend() is not recommended to use . After the suspend() method is called, the thread will not release the occupied resources (such as locks), but occupy the resources and enter the sleep state, which is easy to cause deadlock problems.
What is 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 run() method of the Runnable interface cannot continue to throw exceptions;
What is the difference between
- Volatile can only be used on variables; while synchronized can be used on classes, variables, methods, and code blocks.
- Volatile to ensure visibility; synchronized to ensure atomicity and visibility.
- Volatile disables instruction reordering; synchronized does not.
- Volatile will not cause blocking; synchronized will.
How to control the execution order of threads?
Assuming there are three threads T1, T2, and T3, how do you ensure that T2 is executed after T1 is executed, and T3 is executed after T2 is executed?
You can use the join method solve this problem. For example, in thread A, calling the join method of thread B means : A waits for the execution of thread B to finish (releasing the CPU execution right), and then continues execution.
code show as below:
public class ThreadTest {
public static void main(String[] args) {
Thread spring = new Thread(new SeasonThreadTask("春天"));
Thread summer = new Thread(new SeasonThreadTask("夏天"));
Thread autumn = new Thread(new SeasonThreadTask("秋天"));
try
{
//春天线程先启动
spring.start();
//主线程等待线程spring执行完,再往下执行
spring.join();
//夏天线程再启动
summer.start();
//主线程等待线程summer执行完,再往下执行
summer.join();
//秋天线程最后启动
autumn.start();
//主线程等待线程autumn执行完,再往下执行
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(this.name + "来了: " + i + "次");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
operation result:
春天来了: 1次
春天来了: 2次
春天来了: 3次
夏天来了: 1次
夏天来了: 2次
夏天来了: 3次
秋天来了: 1次
秋天来了: 2次
秋天来了: 3次
Is optimistic locking necessarily good?
Optimistic locking avoids the phenomenon of pessimistic locking exclusive objects and improves concurrency performance, but it also has disadvantages:
- Optimistic locking can only guarantee the atomic operation of a shared variable. If one or more variables are added, optimistic locking will become powerless, but mutual exclusion locks can be easily solved, regardless of the number of objects and the granularity of objects.
- Spinning for a long time may cause high overhead. If the CAS is unsuccessful for a long time and spins all the time, it will bring a lot of overhead to the CPU.
- ABA problem. The core idea of CAS is to judge whether the memory value has been changed by comparing whether the memory value is the same as the expected value, but this judgment logic is not rigorous. If the memory value was originally A, then it was changed to B by a thread, and finally changed to If A, CAS thinks that the memory value has not changed, but it has actually been changed by other threads. This situation has a great impact on the result of the operation of the scenario that depends on the process value. The solution is to introduce the version number, and increase the version number by one every time the 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 a certain task or waits to process certain events. In Java, the garbage collection thread is a special daemon thread.
Communication between threads
volatile
Volatile is a lightweight synchronization mechanism. Volatile guarantees the visibility of variables to all threads and does not guarantee atomicity.
synchronized
Ensure the visibility and exclusivity of thread access to variables.
Waiting for notification mechanism
Wait/notify is a method of the Object object. Calling wait/notify requires obtaining the lock of the object first. After the object calls wait, the thread releases the lock and puts the thread in the waiting queue of the object. When the notification thread calls the notify() method of this object, the waiting thread does not immediately return from wait. It needs to wait for the notification thread to release the lock (notify the thread to execute Finish the synchronization code block), wait for the thread in the queue to acquire the lock, and only after acquiring the lock can it return from the wait() method, that is, the premise of returning from the wait method is that the thread acquires the lock.
The waiting notification mechanism relies on the synchronization mechanism to ensure that the waiting thread can perceive the modification of the variable value of the object when the waiting thread returns from the wait method.
ThreadLocal
Thread local variables. When using ThreadLocal to maintain variables, ThreadLocal provides an independent copy of the variable for each thread that uses the variable, so each thread can change its own copy independently without affecting other threads.
ThreadLocal principle
Each thread has a ThreadLocalMap (ThreadLocal internal class), the key of the elements in the Map is ThreadLocal, and the value corresponds to the thread's variable copy.
Call threadLocal.set()-->call getMap(Thread)-->return the current thread's ThreadLocalMap<ThreadLocal, value>-->map.set(this, value), this is 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);
}
Call get()-->Call getMap(Thread)-->Return the ThreadLocalMap<ThreadLocal, value>-->map.getEntry(this) of the current thread, return value
public T get() {
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;
return result;
}
}
return setInitialValue();
}
The key of the type ThreadLocalMap of threadLocals is the ThreadLocal object, because each thread can have multiple threadLocal variables, 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());
}
}
ThreadLocal is not used to solve the problem of multi-threaded access to shared resources, because the resources in each thread are only copies, not shared. Therefore, ThreadLocal is suitable as a thread context variable to simplify parameter transfer within a thread.
The cause of ThreadLocal memory leak?
Each Thread has an internal attribute of ThreadLocalMap. The key of the map is ThraLocal, which is defined as a weak reference, and the value is a strong reference type. The key is automatically recovered during GC, and the recovery of value depends on the life cycle of the Thread object. Generally, Thread objects are reused to save resources through thread pools, which leads to a longer life cycle of Thread objects, so there is always a strong reference chain relationship: Thread --> ThreadLocalMap-->Entry--> Value, as the task is executed, the value may increase and cannot be released, which will eventually lead to memory leaks.
Solution: Call its remove() method every time you finish using ThreadLocal, and manually delete the corresponding key-value pairs to avoid memory leaks.
currentTime.set(System.currentTimeMillis());
result = joinPoint.proceed();
Log log = new Log("INFO",System.currentTimeMillis() - currentTime.get());
currentTime.remove();
What are the usage scenarios of ThreadLocal?
ThreadLocal applicable scenarios: each thread needs to have its own separate instance, and the instance needs to be shared among multiple methods, that is, to satisfy the isolation between threads and the sharing between methods at the same time. For example, in a Java web application, each thread has its own separate Session instance, which can be implemented using ThreadLocal.
Classification of locks
Fair lock and unfair lock
Acquire object locks in the order of thread access. Synchronized is a non-fair lock. Lock is a non-fair lock by default. It can be set to a fair lock, and a fair lock will affect performance.
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
Shared and exclusive locks
The main difference between shared and exclusive is that only one thread can obtain synchronization state in exclusive mode at the same time, while in shared mode, multiple threads can obtain synchronization state at the same time. For example, a read operation can be performed by multiple threads at the same time, and a write operation can only be performed by one thread at the same time, and other operations will be blocked.
Pessimistic lock and optimistic lock
Pessimistic locks are locked every time a resource is accessed, and the lock is released after the synchronization code is executed. Synchronized and ReentrantLock belong to pessimistic locks.
Optimistic locking does not lock the resource. All threads can access and modify the same resource. If there is no conflict, the modification succeeds and exits, otherwise the loop will continue to try. The most common implementation of optimistic locking is CAS.
Optimistic locking generally has the following two ways:
- It is implemented using the data version recording mechanism, which is the most commonly used implementation of optimistic locking. Adding a version identifier to the data is generally achieved by adding a numeric version field to the database table. When reading data, read the value of the version field together, and add one to the version value every time the data is updated. When we submit an update, we judge that the current version information of the corresponding record in the database table is compared with the version value retrieved for the first time. If the current version number of the database table is equal to the version value retrieved for the first time, it will be updated. Otherwise, it is regarded as expired data.
- Use timestamp. The database table adds a field, the field type uses timestamp (timestamp), which is similar to the version above, also when the update is submitted, the timestamp of the data in the current database is checked and compared with the timestamp taken before the update, if they are consistent, OK, otherwise it is a version conflict.
Applicable scene:
- Pessimistic lock is suitable for scenarios with many write operations.
- Optimistic locking is suitable for scenarios with many read operations, and no locking can improve the performance of read operations.
CAS
What is CAS?
The full name of CAS is Compare And Swap, which is the main implementation method of optimistic locking. CAS realizes variable synchronization between multiple threads without using locks. Both AQS and atomic classes inside ReentrantLock use CAS.
The CAS algorithm involves three operands:
- The memory value V that needs to be read and written.
- The value A to be compared.
- The new value B to be written.
Only when the value of V is equal to A, will the atomic method be used to update the value of V with the new value B, otherwise it will continue to retry until the value is successfully updated.
To AtomicInteger for example, AtomicInteger of getAndIncrement () method of the underlying CAS is realized, the key code is compareAndSwapInt(obj, offset, expect, update)
, its meaning is that if obj
in value
and expect
equal to prove that no other thread changed this variable, then update it to update
If they are not equal, it will continue to retry until the value is successfully updated.
What's the problem with CAS?
Three major issues of CAS:
ABA question . CAS needs to check whether the memory value has changed when operating the value, and update the memory value when there is no change. But if the memory value was originally A, later became B, and then became A, then the CAS will find that the value has not changed when it checks, but it has actually changed. The solution to the ABA problem is to add a version number in front of the variable, and add one to the version number every time the variable is updated, so that the change process changes from
A-B-A
to1A-2B-3A
.JDK has provided the AtomicStampedReference class since 1.5 to solve the ABA problem, and atomically updates reference types with version numbers.
- cycle time and high overhead . If the CAS operation is unsuccessful for a long time, it will spin continuously, which will bring a very large overhead to the CPU.
can only guarantee the atomic operation a shared variable. When operating on a shared variable, CAS can guarantee atomic operation, but when operating on multiple shared variables, CAS cannot guarantee the atomicity of the operation.
Since Java 1.5, the JDK has provided the AtomicReference class to ensure the atomicity between referenced objects, and multiple variables can be placed in one object for CAS operations.
Concurrency tools
Several very useful concurrency tool classes are provided in the JDK concurrency package. The CountDownLatch, CyclicBarrier, and Semaphore tool classes provide a means of concurrent process control.
CountDownLatch
CountDownLatch is used by a thread to wait for other threads execute tasks before executing, similar to thread.join(). A common application scenario is to open multiple threads to perform a task at the same time, and wait until all tasks are executed before performing a specific operation, such as summarizing statistical results.
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); //调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行;等待timeout时间后count值还没变为0的话就会继续执行
System.out.println("task finished");
}
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();
}
}
}
}
operation result:
Thread-0starts working
Thread-1starts working
Thread-2starts working
Thread-3starts working
task finished
CyclicBarrier
CyclicBarrier (synchronization barrier), used for a group of threads to wait for each other to a certain state, and then this group of threads at the same time execution.
public CyclicBarrier(int parties, Runnable barrierAction) {
}
public CyclicBarrier(int parties) {
}
The parameter parties refers to how many threads or tasks are allowed to wait until a certain state; the parameter barrierAction is the content that will be executed when these threads all reach a certain state.
public class CyclicBarrierTest {
// 请求的数量
private static final int threadCount = 10;
// 需要同步的线程数量
private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
public static void main(String[] args) throws InterruptedException {
// 创建线程池
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 block
e.printStackTrace();
}
});
}
threadPool.shutdown();
}
public static void test(int threadnum) throws InterruptedException, BrokenBarrierException {
System.out.println("threadnum:" + threadnum + "is ready");
try {
/**等待60秒,保证子线程完全执行结束*/
cyclicBarrier.await(60, TimeUnit.SECONDS);
} catch (Exception e) {
System.out.println("-----CyclicBarrierException------");
}
System.out.println("threadnum:" + threadnum + "is finish");
}
}
The running results are as follows, we can see that CyclicBarrier can be reused:
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
...
When the four threads all reach the barrier state, one of the four threads will be selected to execute Runnable.
The difference between CyclicBarrier and CountDownLatch
Both CyclicBarrier and CountDownLatch can realize the waiting between threads.
CountDownLatch is used for a thread to wait for the other thread to complete the task before executing. CyclicBarrier for a group of threads waiting for each other to a certain state, then this group of threads and then while execution.
The counter of CountDownLatch can only be used once, while the counter of CyclicBarrier can be reset using the reset() method, which can be used to handle more complex business scenarios.
Semaphore
Semaphore is similar to a lock, it is used to control the number of threads that simultaneously access a specific resource and control 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();
}
}
}
}
The results of the operation are as follows. It can be seen that the resource locks are not acquired in the order of thread access, namely
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 task
Atomic class
Basic type atomic class
Update basic types in an atomic way
- AtomicInteger: Integer atomic class
- AtomicLong: Long integer atomic class
- AtomicBoolean: Boolean atomic class
Commonly used methods of the AtomicInteger class:
public final int get() //获取当前的值
public final int getAndSet(int newValue)//获取当前的值,并设置新的值
public final int getAndIncrement()//获取当前的值,并自增
public final int getAndDecrement() //获取当前的值,并自减
public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
The AtomicInteger class mainly uses CAS (compare and swap) to ensure atomic operations, thereby avoiding the high overhead of locking.
Array type atomic class
Update an element in the array atomically
- AtomicIntegerArray: Integer array atomic class
- AtomicLongArray: Atomic class of long integer array
- AtomicReferenceArray: reference type array atomic class
Common methods of the AtomicIntegerArray class:
public final int get(int i) //获取 index=i 位置元素的值
public final int getAndSet(int i, int newValue)//返回 index=i 位置的当前的值,并将其设置为新值:newValue
public final int getAndIncrement(int i)//获取 index=i 位置元素的值,并让该位置的元素自增
public final int getAndDecrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自减
public final int getAndAdd(int i, int delta) //获取 index=i 位置元素的值,并加上预期的值
boolean compareAndSet(int i, int expect, int update) //如果输入的数值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值(update)
public final void lazySet(int i, int newValue)//最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
Reference type atomic class
- AtomicReference: reference type atomic class
- AtomicStampedReference: Reference type atomic class with version number. This class associates integer values with references, which can be used to solve atomic update data and data version numbers, and can solve ABA problems that may occur when using CAS for atomic updates.
- AtomicMarkableReference: Atomic update of marked reference types. This class associates boolean tags with references
To share a github repository, put on top of 200 Duo classic computer books , including the C language, C ++, Java, Python, front-end, database, operating systems, computer networks, data structures and algorithms, machine learning, programming life Wait, you can star, next time you find a book, search directly on it, the warehouse is continuously updating~
github address: https://github.com/Tyson0314/java-books
If github is not accessible, you can visit the gitee repository.
gitee address: https://gitee.com/tysondai/java-books
Java concurrency
Hi everyone, this is Da Bin. In the recent interview, I read a lot of interview questions and summarized the common interview questions of Java concurrent programming. If you are helpful, you can collect and . will continue to update new interview questions in the future!
The article catalog is as follows:
Thread Pool
Thread pool: A pool for managing threads.
Why use thread pool?
- reduces resource consumption . Reduce the consumption caused by thread creation and destruction by reusing the created threads.
- improves the response speed . When the task arrives, the task can be executed immediately without waiting until the thread is created.
- improves thread manageability . Unified management of threads, to avoid the system creating a large number of threads of the same type and causing the memory to run out.
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);
How does the thread pool execute?
To create a new thread, you need to acquire a global lock. Through this design, you can avoid acquiring a global lock as much as possible. After the ThreadPoolExecutor finishes warming up (the number of currently running threads is greater than or equal to corePoolSize), most of the submitted tasks will be placed in the BlockingQueue.
In order to vividly describe the thread pool execution, make an analogy:
- The core thread is compared to the company's full-time employees
- Non-core threads are compared to outsourced employees
- The blocking queue is compared to the demand pool
- Comparing the submission of tasks to the requirements
What are the thread pool parameters?
The general constructor of ThreadPoolExecutor:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);
- corePoolSize: When there is a new task, if the number of threads in the thread pool does not reach the basic size of the thread pool, a new thread will be created to perform the task, otherwise the task will be placed in the blocking queue. When the number of surviving threads in the thread pool is always greater than corePoolSize, you should consider increasing corePoolSize.
- maximumPoolSize: When the blocking queue is full, if the number of threads in the thread pool does not exceed the maximum number of threads, a new thread will be created to run the task. Otherwise, the new task will be processed according to the rejection policy. Non-core threads are similar to temporarily borrowed resources. These threads should exit after their idle time exceeds keepAliveTime to avoid waste of resources.
- BlockingQueue: Store tasks waiting to be run.
- keepAliveTime: non-core thread idle, keep alive time, this parameter is only valid for non-core threads. Set to 0, which means that redundant idle threads will be terminated immediately.
TimeUnit: Time unit
TimeUnit.DAYS TimeUnit.HOURS TimeUnit.MINUTES TimeUnit.SECONDS TimeUnit.MILLISECON
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。