40 questions let you quickly grasp the essence of Java multithreading

40 questions let you quickly grasp the essence of Java multithreading

Abstract: Multithreading can be understood as the ability to run multiple different threads at the same time to perform different tasks in the same program, and these threads can use multiple cores of the CPU to run at the same time.

This article is shared from the HUAWEI CLOUD community "I feel confused about the usage of Java multithreading? 40 questions let you quickly grasp the essence of multithreading" , the original author: breakDraw.

Multithreading can be understood as the ability to run multiple different threads at the same time to perform different tasks in the same program, and these threads can use multiple cores of the CPU to run at the same time. Multi-threaded programming can maximize the use of CPU resources. This article will explain the usage of multithreading in the following directions.

  • 1. Thread basics
  • 2.synchronized keyword
  • 3. Other synchronization tools
  1. CountDownLatch
  2. FutureTask
  3. Semaphore
  4. CyclicBarrier
  5. Exchanger
  6. Atomic class AtomicXXX
  • 4. Thread pool
  • 5.Thread state transition
  • 6. Volatile
  • 7. Thread group

1. Thread class foundation

Q: What are the 3 deprecated expiration methods of Thread? What is the role
A:

  • stop(), terminate the execution of the thread.
  • suspend(), suspend thread execution.
  • resume(), resume thread execution.

Q: What is the reason for the abandonment of stop?
A: When stop is called, the thread will be directly terminated and the locked lock on the thread will be released. The thread cannot be sensed and will not do the catch operation in the thread. ! That is, the mess after stop will not be handled inside the thread. If other threads are waiting for the above lock to fetch data, they may get a semi-finished product.

If it becomes a question, it should look like the following. What will be output?

public class Test { public static void main(String[] args) throws InterruptedException { System.out.println("start"); Thread thread = new MyThread(); thread.start(); Thread.sleep(1000); thread.stop(); //thread.interrupt(); } } class MyThread extends Thread { public void run() { try { System.out.println("run"); Thread.sleep(5000); } catch (Exception e) { //Handle the mess, clean up resources System.out.println("clear resource!"); } } } Copy code

The answer is to output start and run, but not clear resource

Q: What is the alternative to stop?
A: interrupt().
When thread.interrupt() is called to terminate, the lock will not be released directly. You can call interrupt() or catch the interrupt exception generated by sleep to determine whether it is terminated and deal with the mess.

In the above question, change thread.stop() to thread.interrupt(). InterrupException will be thrown during Thread.sleep() (note that InterrupExcetpion is thrown by sleep), so clear resource will be output. If you haven't done sleep operation, you can use isInterrupted() to judge whether your thread has been terminated and clean up.

Also note the difference between interrupt and isInterrupted:

Q: What is the reason for the abandonment of suspend/resume?
A: Calling suspend will not release the lock.
If thread A pauses and its resume is called by thread B, but thread B relies on a certain lock in A, then it is deadlocked. For example, in the following example, we must know that it will cause a deadlock:

public class Test { public static Object lockObject = new Object(); public static void main(String[] args) throws InterruptedException { System.out.println("start"); Thread thread = new MyThread(); thread.start(); Thread.sleep(1000); System.out.println("The main thread tries to occupy lockObject lock resources"); synchronized (Test.lockObject) { //do something with Test.lockObject System.out.println("Do something"); } System.out.println("Restore"); thread.resume(); } } class MyThread extends Thread { public void run() { try { synchronized (Test.lockObject) { System.out.println("Occupy Test.lockObject"); suspend(); } System.out.println("MyThread releases TestlockObject lock resource"); } catch (Exception e){} } } Copy code

Answer output

After MyThread is internally suspended, the external main cannot obtain the lock, so the subsequent resume operation cannot be performed.

Q: How can the suspend and resume in the above question be replaced to solve the deadlock problem?
A: You can use wait and noitfy to deal with (but try not to design this way, generally use run with a while loop inside)

public class Test { public static Object lockObject = new Object();//Used as a temporary lock object public static void main(String[] args) throws InterruptedException { Thread thread = new MyThread(); thread.start(); Thread.sleep(1000); System.out.println("The main thread tries to occupy lockObject lock resources"); synchronized (Test.lockObject) { //do something with Test.lockObject System.out.println("Do something"); } System.out.println("Restore"); synchronized (Test.lockObject) { Test.lockObject.notify(); } } } class MyThread extends Thread { public void run() { try { synchronized (Test.lockObject) { System.out.println("Occupy Test.lockObject"); Test.lockObject.wait(); } System.out.println("MyThread releases TestlockObject lock resource"); } catch (Exception e){} } } Copy code

Performed in this way, the result is normal:

Q: Why does the following example run abnormally and throw an IllegalMonitorStateException error?

public static void main(String[] args) throws InterruptedException { Thread thread = new MyThread(); thread.start(); thread.notify(); } Copy code

A: The premise of using notify and wait is that the lock of this object must be held, that is, the main code block needs to hold the lock of the thread object before using notify to wake up (the same applies to wait).

Just change it to the following:

Thread thread = new MyThread(); thread.start(); synchronized (thread) { thread.notify(); } Copy code

Q: The difference between Thread.sleep() and Object.wait()
A: sleep will not release the object lock, while wait will release the object lock.

Q: The difference between Runnable interface and Callable.
A: Callable can work with Futrue, and when you start the thread, you can use the call to get the return value after the thread ends, and the call method can also throw an exception.

Q: thread.alive() indicates whether the thread is currently active/available.
Active state: The thread has started and has not yet terminated. If the thread is running or ready to start running, the thread is considered to be "alive

After thread.start(), does alive() always return true?

public class Main { public static void main(String[] args) { TestThread tt = new TestThread(); System.out.println("Begin == "+ tt.isAlive()); tt.start(); System.out.println("end == "+ tt.isAlive()); } } Copy code

A: Not necessarily. It is possible that when printing, the thread has finished running, or after start, it has not really started (that is, it has not yet entered the run)

Q: Thread A is as follows:

public class A extends Thread { @Override public void run() { System.out.println("this.isAlive()=" + this.isAlive()); } } Copy code

Pass thread A as the construction parameter to thread B

A a = new A(); Thread b = new Thread(a); b.start() Copy code

What will be printed at this time?
A: It will print false at this time!

Because a is passed into b as a construction parameter, when b executes start, the run method of the A object is actually called in the B thread, rather than the A thread is enabled.

If changed to

A a = new A(); a.start() Copy code

Then it will print true

Q: After putting FutureTask into Thread and starting, will the content in callable be executed normally?

public static void main(String[] args) throws Exception { Callable<Integer> callable = () -> { System.out.println("call 100"); return 100; }; FutureTask<Integer> task = new FutureTask<>(callable); Thread thread = new Thread(task); thread.start(); } Copy code

A: Can print normally

2. synchronized keyword

  • Can be used as a modifier for methods or as a modifier for code blocks

  • Note that when modifying the method, it is not that there is a lock on this method, but when the method is called, the lock on the object where the method is located needs to be taken.

    class A{ synchroized f(){}
    }

That is, calling this f() does not mean that f can only be entered once at a time, but that when entering f, the lock on A needs to be obtained.

Q: Will there be a deadlock when calling f() below?

class A{ synchroized f(){ t() } synchroized t(){ } } Copy code

A: No.
Within 1 thread, you can repeatedly enter the synchroized block of 1 object.

  • Principle :
    When a thread requests its own lock. The JVM will keep track of the lock holder and count the lock as 1. If the thread requests its own lock again, it can enter again, and the count is 2. Count -1 when exiting, and the lock will not be released until all exits.

Q: Will two threads call f1 and f2 at the same time cause synchronization?

class A{ private static synchronized void f1(){}; private synchronized void f2(){}; } Copy code

A: No synchronization will occur. Both are not a lock.
f1 is a class lock, equivalent to synchronized (A.class)
f2 is an object lock.

3. other synchronization tools

CountDownLatch

final CountDownLatch latch = new CountDownLatch(2); Copy code

2 is the initial value of the counter.

Then when latch.await() is executed, it will block until the latch is executed by latch.countDown() in other threads and the counter is reduced to 0.

  • The difference with join : When
    join is blocked, it only waits for the completion of a single thread,
    while CountDownLatch may be to wait for multiple threads

Q: Can the internal count value of countDownLatch be reset?
A: Can't reset it. If you want to recount, you must renew a new one. After all, his class name is DownLatch

FutureTask

It can be understood as a thread that supports a return value
FutureTask task = new FutureTask<>(runable);
When task.get() is called, the return value in the thread can be reached

Q: When calling futrueTask.get(), is this a blocking method? If it is blocked, when will it end?
A: It is a blocking method.

  1. The thread runs and returns the result
  2. The blocking time reaches the xxx time set in futrueTask.get(xxx)
  3. The thread has an exception InterruptedException or ExecutionException
  4. The thread is cancelled, throwing a CancellationException

Semaphore

Semaphore: It is the concept that is common in operating systems, implemented in Java, and used for resource coordination among threads.
Use Semaphore (permits) to construct a semaphore containing permits resources, and then a thread performs a consumption action, then executes semaphore.acquire(), and a resource is consumed. If a thread does a production action, execute semaphore.release (), a resource will be released (that is, a new resource will be added)

More detailed semaphore method description:
blog.csdn.net/hanchao5272...

Q: What is the difference between fair mode and unfair mode in semaphore? The following is set to true is the fair mode

//new Semaphore(permits,fair): initialize the number of licenses and the constructor for fairness mode semaphore = new Semaphore(5, true); Copy code

A: What kind of fair lock or unfair lock is actually used.

The main differences between fairSync and NonfairSync in Java concurrency are:

  • If the current thread is not the owner of the lock, NonfairSync does not judge whether there is a waiting queue, and directly uses compareAndSwap to occupy the lock, that is, whoever just grabs it will use it!
  • If the current thread is not the owner of the lock, FairSync will determine whether there is a waiting queue, and if there is, it will add itself to the end of the waiting queue, that is, strictly first come first served!

CyclicBarrier

Fences are generally called in threads. Its construction needs to specify the number of threads and the operations to be performed before the barrier is destroyed. Whenever a thread calls barrier.await(), it will enter blocking, and the thread count in the barrier is -1.
When the thread count reaches 0, the operation specified in the barrier is called, and then the barrier is destroyed, and all threads blocked on await continue to go down.

Exchanger

I understand it as a two-sided fence, used to exchange data.
Simply put, a thread wants to exchange data with another thread after completing a certain transaction. The first thread that takes out the data will wait for the second thread until the second thread arrives with the data. Exchange corresponding data.

Atomic class AtomicXXX

That is, the internal atomic synchronization mechanism has been implemented.
Q: What is the output below? (Examine the usage of getAndAdd)

AtomicInteger num = new AtomicInteger(1); System.out.println(num.getAndAdd(1)); System.out.println(num.get()); Copy code

A: Output 1
, 2 as the name implies, getAndAdd(), then get first, then add, similar to num++.
If it is addAndGet(), then it is ++num

Q: The difference between AtomicReference and AtomicInteger?
A: AtomicInteger is an encapsulation of integers, while AtomicReference corresponds to ordinary object references. That is, it can ensure the thread safety when you modify the object reference. That is, multiple threads may modify the references contained in atomicReference.

  • Classic usage:
    boolean exchanged = atomicStringReference.compareAndSet(initialReference, newReference) is the classic CAS synchronization method
    compreAndSet, which compares the reference with the expected value (reference), and if they are equal, set a new reference in the AtomicReference object. Similar to a non-responsible spin lock.
  • AtomicReferenceArray is an atomic array, you can perform some atomic array operations such as set(index, value),

All atomic classes implemented in java:

Note that there is no float, no short and byte.

4. thread pool

Q: Among the ThreadPoolExecutor thread pool construction parameters, what is the difference between corePoolSize and maximumPoolSize?
A: When submitting a new thread to the pool

  • If the current number of threads <corePoolSize, a new thread will be created
  • If the current number of threads = corePoolSize, the new thread is stuffed into a queue to wait.
  • If the queue is also full, then it will start to create a new thread to run the task, to avoid task blocking or discarding
  • If the queue is full, the total number of threads exceeds the maximuminumPoolSize, then throw an exception or block (depending on the nature of the queue).
  • Call prestartCoreThread() to start an idle core thread in advance
  • Call prestartAllCoreThreads() to create corePoolSize core threads in advance.

Q: What does the keepalive parameter of the thread pool do?
A: When the number of threads is between corePoolSize and maxinumPoolSize, if any thread has run out and the idle time exceeds keepalive, it will be cleared (note that it is only limited to threads between corePoolSize and maxinumPoolsize)

Q: What are the three queue strategies for thread pools?
A:

  1. The handshake queue is
    equivalent to a non-queuing queue. May cause the number of threads to grow indefinitely until it exceeds maxinumPoolSize (equivalent to corePoolSize is useless, only maxinumPoolSize is the upper limit)
  2. Unbounded queue The
    queue length is unlimited, that is, when the number of threads reaches corePoolSize, the subsequent threads will only wait in the queue. (Equivalent to maxinumPoolSize is useless)
    Defect: It may cause the queue to grow indefinitely and even OOM
  3. Bounded queue

Q: What are the rejection strategies when the thread pool queue is full and maxinumPoolSize is full?
A:

  • AbortPolicy default policy: throw RejectedExecutionException directly
  • DiscardPolicy discard strategy: directly lost, no error is reported
  • DiscardOldestPolicy discards the leader of the team strategy: that is, throw the first person into the team from the leader of the team, and then try to make the task enter the end of the team (the innermost task of the leader of the team: unfair...)
  • CallerRunsPolicy caller processing strategy: hand it over to the thread where the caller is located to run the task (that is, whoever calls submit or execute, he will run by himself)
  • You can also use to implement a custom new RejectedExecutionHandler

Q: There are the following five types of thread pools provided by Executor. Just remember their purpose to understand the internal principles.

  • newCachedThreadPool: Cached thread pool
    corePoolSize=0, maxinumPoolSize=+ , queue length=0, so the number of threads will be flexibly cached and changed from corePoolSize to maxinumPoolSize, and there is no queue waiting situation. I create a task as soon as it comes. It will be released when it is used up.

  • newFixedThreadPool: fixed-length thread pool
    corePoolSize= maxinumPoolSize= construction parameter value, queue length=+ . Therefore, there is no expansion when there are not enough threads
  • newScheduledThreadPool: timer thread pool is used to
    submit timed tasks, the interval and unit of the timer will be included in the construction parameters. Others are the same as FixedThreadPool and belong to a fixed-length thread pool.
  • newSingleThreadExecutor: single-thread pool
    corePoolSize=maxinumPoolSize=1, queue length=+ , only one task will run, so other tasks will wait in the queue, so it will be executed strictly according to FIFO
  • newWorkStealingPool (inherited from ForkJoinPool): Parallel thread pool
    If your task execution time is very long, and the tasks inside run in parallel, then it will subdivide your thread tasks into other threads to divide and conquer. ForkJoinPool introduction: blog.csdn.net/m0_37542889...

Q: What is the difference between submit and execute?
A:

  • Execute can only receive Runnable tasks, and submit can receive Callable in addition to Runnable (Callable tasks support return values)
  • The execute method returns void, and the submit method returns FutureTask.
  • In terms of exceptions, because the submit method returns a futureTask object, when future.get() is performed, an exception in the thread will be thrown, so the caller can easily handle the exception. (If it is execute, you can only use internal capture or set catchHandler)

Q: What is the difference between shutdown, shutdownNow, and awaitTermination in the thread pool?
A:

  • shutdown: Stop receiving new tasks and wait for the completion of all existing tasks in the pool ( including threads in the waiting queue ). Asynchronous method, which returns immediately after calling.
  • shutdownNow: stop receiving new tasks, and stop all task being executed , the return task list still in the queue.
  • awaitTermination: It is just a judgment method to judge whether the current thread pool tasks have all ended. Generally used after shutdown, because shutdown is an asynchronous method, you need to know when it really ends.

5. Thread state transition

Q: The 6 states of threads are:
A:

  • New: A new thread is created, but start has not been called yet
  • RUNNABLE: running, the ready state is included in the running state
  • BLOCKED: Blocked, usually because you want to get the lock and can't get it
  • WAITING: Waiting, usually after wait or join
  • TIMED_WAITING: Timed waiting, that is, it can return after a fixed time, usually by calling sleep or wait (time).
  • TERMINATED: Termination status.

Appreciating a good picture, you can understand which methods will enter which states are called.

Original image link
Q: When will the java thread enter blocking (may be tested by multiple choice questions):
A:

  • sleep
  • wati() hangs, waiting for Notify() messages sent by other threads
  • Wait for IO
  • Waiting for lock

6. Volatile

When using volatile to modify a member variable, once a thread modifies the variable, other threads can immediately see the change.

Q: When the member variable is not modified with volatile, why can't other threads see the change immediately?
A: Threads can store variables in local memory (such as machine registers) instead of reading and writing directly in main memory.
This may cause one thread to modify the value of a variable in the main memory, while another thread continues to use its variable value in the register.

Q: Can I use volatile without locking?
A: No way.

  • Locking does not only guarantee the mutual exclusion of one variable. Sometimes it is necessary to ensure that when several members change continuously, other threads cannot interfere and read.
  • While volatile guarantees that one variable is variable, it cannot guarantee the atomicity when several variables change at the same time.

Q: Show a classic example from the book "Java Concurrent Programming Practice", which also appeared in the second exam of the subject, but the example was changed. Why the following example may have an endless loop or output 0?

A: First understand java reordering, you can read this blog post: www.cnblogs.com/coshaho/p/8...

Then analyze how the latter two strange situations happened.

  • Never output:
    After the program's instruction sequence, this situation occurs:
  1. ReaderThread reads the ready value in while, which is false at this time, so it is stored in the register of ReaderThread.
  2. The main thread modifies ready and number.
  3. ReaderThread does not perceive the modification of ready (for ReaderThread thread, it does not perceive the relevant instructions to let him update the value of ready register), so it enters an endless loop.
  • The output 0
    is sorted by the instructions of the program, and this situation occurs:
    1) The main thread sets ready to true
    2) ReaderThread reads the ready value in the while, and it is true, so it exits the while loop
  1. ReaderThread reads the value of number, and the initial value of number is 0 at this time, so it outputs 0
  2. The main thread only modifies number=42 at this time, and the ReaderThread has ended at this time!

For the above problem, you can use volatile or lock. When you add a lock, if the variable is written, there will be an instruction to update the value of another register, so it will be visible.

7. thread group

In order to facilitate the management of a batch of threads, we use ThreadGroup to represent the thread group, and use it to classify and manage a batch of threads

Instructions:

Thread group = new ThreadGroup("group"); Thread thread = new Thread(gourp, ()->{..}); Copy code

That is, in addition to the Thread (Runable) construction method, there is also a Thread (ThreadGroup, Runnable) construction method.

Q: Create thread B in thread A, do they belong to the same thread group?
A: Yes

A major function of the thread group is to perform unified exception capture processing on the same group of threads, avoiding the need to go to setUncaghtExceptionHandler every time a new thread is created. That is, the thread group itself can implement an uncaughtException method.

ThreadGroup group = new ThreadGroup("group") { @Override public void uncaughtException(Thread thread, Throwable throwable) { System.out.println(thread.getName() + throwable.getMessage()); } }; } Copy code

If a thread throws an exception and is not caught inside the thread, what is the processing sequence of the thread exception at this time? I believe that many people have read the following passage, and many blogs about thread groups write this:
(1) 1. check whether the current thread group (ThreadGroup) has a thread group of the parent class. If so, use the parent class s thread group. UncaughtException() method.
(2) If not, it depends on whether the thread calls the setUncaughtExceptionHandler() method to create an instance of Thread.setUncaughtExceptionHandler. If it is established, directly use its UncaughtException() method to handle the exception.
(3) If none of the above is true, it depends on whether the exception is a ThreadDead instance, if it is, do nothing, if not, output the stack trace information (printStackTrace).

Source:
blog.csdn.net/qq_43073128...
blog.csdn.net/qq_43073128...

Okay, don t worry about it. Let s take a look at the following questions and ask what to output:
Q:

//Parent thread group static class GroupFather extends ThreadGroup { public GroupFather(String name) { super(name); } @Override public void uncaughtException(Thread thread, Throwable throwable) { System.out.println("groupFather=" + throwable.getMessage()); } } public static void main(String[] args) { //subclass thread group GroupFather groupSon = new GroupFather("groupSon") { @Override public void uncaughtException(Thread thread, Throwable throwable) { System.out.println("groupSon=" + throwable.getMessage()); } }; Thread thread1 = new Thread(groupSon, ()->{ throw new RuntimeException("I am abnormal"); }); thread1.start(); } Copy code

A: Looking at (1), shouldn't it output groupFather?

Wrong wrong wrong, the output is the sentence groupSon can be seen in many places, but people who have not practiced the source code will be misled. In fact, the parent thread group does not refer to the thread group in the class inheritance relationship, but refers to the following:

That is to say, there is a parent-child relationship that constitutes a relationship. If the threadGroup of the subclass does not implement the uncaughtException method, then it will construct the parent thread group specified in the parameter to call the method.

Q: Then I changed it to the parent-child relationship in the structure relationship. What is the output below?

public static void main(String[] args) { //parent thread group ThreadGroup groupFather = new ThreadGroup("groupFather") { @Override public void uncaughtException(Thread thread, Throwable throwable) { System.out.println("groupFather=" + throwable.getMessage()); } }; //Child thread group, take groupFather as the parent parameter ThreadGroup groupSon = new ThreadGroup(groupFather, "groupSon") { @Override public void uncaughtException(Thread thread, Throwable throwable) { System.out.println("groupSon=" + throwable.getMessage()); } }; Thread thread1 = new Thread(groupSon, ()->{ throw new RuntimeException("I am abnormal"); }); thread1.start(); } Copy code

A: Answer output

That is, as long as the child thread group has been implemented, the method in the child thread group will be used instead of the parent thread group directly looking for!

Q: What if I let myself do the set catcher operation? What is the output of the following?

public static void main(String[] args) { //parent thread group ThreadGroup group = new ThreadGroup("group") { @Override public void uncaughtException(Thread thread, Throwable throwable) { System.out.println("group=" + throwable.getMessage()); } }; //Create a thread in the thread group Thread thread1 = new Thread(group, () -> { throw new RuntimeException("I am abnormal"); }); //Set up the setUncaughtExceptionHandler method yourself thread1.setUncaughtExceptionHandler((t, e) -> { System.out.println("no gourp:" + e.getMessage()); }); thread1.start(); } Copy code

A: Looking at the previous conclusion, it seems that the exception of the thread group should be output?
But the result is:

In other words, if the thread has specifically executed setUncaughtExceptionHandler on itself, then it has priority to deal with the UncaughtExceptionHandler set by itself.

Is this point (2) wrong? It's really wrong. In fact, the second point should refer to the default catcher of the global Thread. Note that it is global. In fact, that paragraph comes from the source code of uncaughtException in ThreadGroup:

The previous three points are explained here, but the code does not consider the thread itself to set the catcher

So modify the previous summary of the actual exception throwing judgment logic of the thread:

  1. If the thread itself has setUncaughtExceptionHandler, use the one set by itself.

  2. If it has not been set, look at there is no thread group. And judge according to the following logic:
    If the thread group has overwritten uncaughtException, use the overwritten uncaughtException.
    If the thread group has not overwritten, then go to the uncaughtException method of the parent thread group (note the concept on the structure).

  3. If neither the thread group nor the parent class has overridden uncaughtException, then judge whether to use Thread.setDefaultUncaughtExceptionHandler(xxx) to set the global default catcher, if any, use the global default

  4. If it is not a ThreadDeath thread, only the stack is printed.

  5. If it is a ThreadDeath thread, then nothing is processed.

Click to follow to learn about Huawei Cloud's fresh technology for the first time~