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

Confused about Java multithreading?40 questions to Learn the essence of Multithreading quickly.

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

  • 1. The Thread class
  • 2. The synchronized keyword
  • 3. Use other synchronization tools
  1. CountDownLatch
  2. FutureTask
  3. Semaphore
  4. CyclicBarrier
  5. Exchanger
  6. Atomic classes AtomicXXX
  • 4. The thread pool
  • 5. Change the Thread status
  • 6.Volatile
  • 7. Thread groups

First, the foundation of Thread class

Q: What are the three deprecated expiration methods of Thread? What does it do?

  • Stop () terminates the execution of the thread.
  • Suspend (), suspends the thread execution.
  • Resume () to resume thread execution.

Q: What was the reason for abandoning stop? A: When stop is called, it will terminate the thread and release the locked lock on the thread. The thread is not aware of it and will not do the catch operation in the thread! That is, the thread does not handle the stop mess internally. If another thread is waiting for the lock to fetch data, it might get a half-finished product.

It would look something like this. What would be the 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) {system.out. println("clear resource!" ); }}}Copy the code

The answer is to print start and run, but not clear Resource

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

Thread.stop () instead of thread.interrupt() will throw interrupException during thread.sleep (). InterrupExcetpion is thrown by sleep) so the clear Resource is printed. If you do not have a sleep, you can use isInterrupted() to see if your thread has been interrupted.

Note also the difference between interrupt and isInterrupted:

Q: Why was suspend/resume abandoned? A: : Calling suspend does not release the lock. If thread A suspends and its resume is called by thread B, but thread B relies on A lock in thread A, then it is deadlocked. For example, the following example causes 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 is trying to occupy the lock resource of the lockObject "); Synchronized (test.lockObject) {// use test.lockObject to do something system.out.println (" do something "); } system.out.println (" restore "); thread.resume(); } } class MyThread extends Thread { public void run() { try { synchronized (Test.lockObject) { System. The out. Println (" take up the Test. LockObject "); suspend(); } system.out. println("MyThread releases the TestlockObject lock resource "); } catch (Exception e){} } }Copy the code

The answer to the output

If MyThread is paused inside, the external main cannot resume because it cannot acquire the lock.

Q: How can suspend and resume be replaced to resolve the deadlock problem? A: Wait and noitfy can be used to handle this (try not to do this, usually run with A while loop inside)

public class Test { public static Object lockObject = new Object(); Public static void main(String[] args) throws InterruptedException {Thread Thread = new MyThread(); thread.start(); Thread.sleep(1000); System.out.println(" The main thread is trying to occupy the lock resource of the lockObject "); Synchronized (test.lockObject) {// use test.lockObject to do something 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. The out. Println (" take up the Test. LockObject "); Test.lockObject.wait(); } system.out. println("MyThread releases the TestlockObject lock resource "); } catch (Exception e){} } }Copy the code

When executed this way, the result is normal:

Q: why did the following example will run abnormalities, throw IllegalMonitorStateException mistake?

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

A: Notify and wait can be used only if the main block holds the lock on thread.

Change it to the following:

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

Sleep () and Object.wait() A: Sleep does not release Object locks, while wait releases Object locks.

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

Q: Thread.alive () indicates whether the thread is currently active or available. Active: The thread has been started and has not been terminated. A thread is considered alive if it is running or ready to start running

Does alive() always return true after thread.start()?

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 the code

A: Not necessarily. It may be that the thread has already finished running at the time of printing, or that the thread has not actually started after the start (i.e. has not 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 the code

Pass thread A as A construction parameter to thread B

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

What is printed at this point? A: False!

Since a is passed to B as a construction parameter, b actually calls the run method of a object in thread B, rather than starting thread A.

If change to

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

Then it prints true

Q: After FutureTask is put into Thread and start, will the contents of 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 the code

A: It can print normally

Synchronized keyword

  • Can be used as a modifier for a method or a code block

  • Note that the method does not have a lock on it, but rather that the method is called with a lock on the object on which the method resides.

    class A{ synchroized f(){ }

    }

So when YOU call f(), it doesn’t mean that f can only enter once at A time, it means that when you enter F, you need to get the lock on A.

Q: Does a deadlock occur when the following f() is called?

class A{
     synchroized f(){
        t()
     }
 
     synchroized t(){
     }
}
Copy the 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 takes note of the lock holder and gives the lock a count of 1. If the thread requests its lock again, it can re-enter, with a count of 2. Count -1 on exit and lock will not be released until all exits.

Q: Will synchronization occur when two threads call F1 and F2 at the same time?

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

A: No synchronization occurs. The two are not one lock. F1 is a class lock, equivalent to synchronized(a.class) and F2 is an object lock.

Other synchronization tools

CountDownLatch

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

2 is the initial value of the counter.

When latch.await() is executed, it blocks until the latch is latched out of latch.countdown () in another thread and the counter is reduced to zero.

  • The difference between a JOIN and CountDownLatch is that a join blocks while waiting for a single thread to complete. CountDownLatch can wait for multiple threads

Q: Can the internal count of countDownLatch be reset? A: It can’t be reset. If you want to recalculate, you have to do a new one. After all, his class name is DownLatch

FutureTask

FutureTask = new FutureTask<>(runable); When task.get() is called, the return value in the thread is reached

Q: Is this a blocking method when futruetask.get () is called? If so, when will it end? A: It’s A blocking method.

  1. The thread finishes and returns the result
  2. The blocking time reaches the XXX time set in futruetask.get (XXX)
  3. InterruptedException or ExecutionException has occurred in the thread
  4. The thread is cancelled, throwing a CancellationException

Semaphore

Semaphore: a common concept in operating systems, implemented in Java, used to coordinate resources between threads. Semaphore.acquire (), semaphore.release(), semaphore.release(), Semaphore(permitting), Semaphore(permitting), Semaphore(permitting), Semaphore(permitting), Semaphore(permitting), Semaphore(permitting) A resource is released (that is, a new resource is added)

More detailed descriptions semaphore method: blog.csdn.net/hanchao5272…

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

Permits = new Semaphore(5, true); permits = new Semaphore(5, true); permits = new Semaphore(fair): permits = new Semaphore(5, true);Copy the code

A: It’s just A matter of which lock is fair or not.

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 determine whether there is a wait queue and uses compareAndSwap to grab the lock.
  • If the current thread is not the owner of the lock, Then FairSync determines if there is a wait queue and if there is, it adds itself to the end of the wait queue, strictly first come, first served!

CyclicBarrier

Fences are usually invoked in threads. It is constructed to specify the number of threads to perform before the barrier is broken. Each time a thread calls barrier. Await (), it blocks and the barrier counts -1. When the thread count is 0, after calling the operation specified in the fence, the fence is broken and all threads blocked on the await continue to go down.

Exchanger

I understand it as two sides of fence, used to exchange data. To put it simply, when a thread wants to exchange data with another thread after completing a certain transaction, the first thread that takes out data first will wait for the second thread until the second thread arrives with the data before exchanging corresponding data with each other.

Atomic classes AtomicXXX

Q: What is the output? (Examine the use of getAndAdd)

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

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

Q: What is the difference between AtomicReference and AtomicInteger? A: An AtomicInteger encapsulates an integer, and an AtomicReference corresponds to A normal object reference. That is, it ensures thread-safety when you modify object references. That is, multiple threads may modify references contained in the atomicReference.

  • Classic usage: boolean exchanged = atomicStringReference.compareAndSet(initialReference, NewReference) is the classic CAS synchronization method compreAndSet, which will compare the reference to the expected value (reference), and if they are equal, set a newReference in the AtomicReference object. It’s like an irresponsible spin lock.
  • AtomicReferenceArray is an atomic array that can perform some atomic array operations such as set(index, value),

All atomic classes implemented in Java:

Notice that there are no floats, no shorts, and no bytes.

Thread pools

Q: What is the difference between corePoolSize and maximumPoolSize in ThreadPoolExecutor thread pool construction parameters? A: When A new thread is submitted to the pool

  • If the current thread count is < corePoolSize, a new thread is created
  • If the current thread count =corePoolSize, the new thread is pushed into a queue to wait.
  • If the queue is also full, a new thread is started to run the task, preventing the task from being blocked or discarded
  • If the queue is full and the total number of threads exceeds maxinumPoolSize, an exception is thrown or blocked (depending on the nature of the queue).
  • A call to prestartCoreThread() preempts an idle core thread
  • Call prestartAllCoreThreads() to pre-create corePoolSize core threads.

Q: What is the keepalive parameter for a thread pool? A: When the number of threads is between corePoolSize and maxinumPoolSize, if any thread has run out and has been idle for longer than keepalive, the thread will be cleared.

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

  1. Handshake queues are equivalent to unqueued queues. May cause the number of threads to grow indefinitely until they exceed the maxinumPoolSize limit.
  2. Unbounded queue The queue length is infinite, that is, when the number of threads reaches corePoolSize, subsequent threads only wait in the queue. Bug: May cause queues to grow infinitely to the point where OOM
  3. Bounded queue

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

  • AbortPolicy default policy: direct throw RejectedExecutionException anomalies
  • DiscardPolicy: If it is lost, no error is reported
  • DiscardOldestPolicy: Discarding the team leader Strategy by discarding the first player from the team leader and then attempting to get the player to the end of the team.
  • CallerRunsPolicy CallerRunsPolicy CallerRunsPolicy CallerRunsPolicy CallerRunsPolicy CallerRunsPolicy CallerRunsPolicy CallerRunsPolicy
  • You can also use the implementation to customize the new RejectedExecutionHandler

Q: There are five Executor thread pools. Remember what they are used for to understand how they work.

  • NewCachedThreadPool: Cache thread pool corePoolSize=0, maxinumPoolSize=+∞, queue length =0, so the number of threads will cache flexibly between corePoolSize and maxinumPoolSize, and there is no queue waiting situation. It will be released when it’s finished.

  • NewFixedThreadPool: fixed-length thread pool corePoolSize= maxinumPoolSize= construction parameter value, queue length =+∞. Therefore, there is no case for scaling when there are not enough threads
  • NewScheduledThreadPool: used by the timer thread pool to submit scheduled tasks. The construction parameter contains the timer interval and unit. Other thread pools belong to the same fixed length thread pool as fixedThreadPools.
  • NewSingleThreadExecutor: Single thread pool corePoolSize=maxinumPoolSize=1, queue length =+∞, only one task will run, so other tasks will wait in the queue, so strictly FIFO execution
  • NewWorkStealingPool (inherited from ForkJoinPool) : If your task takes a long time to execute, and the tasks in it run in parallel, it will subdivide your task into other threads. ForkJoinPool: blog.csdn.net/m0_37542889…

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

  • Execute can only accept Runnable tasks. Submit can accept Callable tasks as well as Runnable tasks.
  • The execute method returns void, and the submit method returns FutureTask.
  • On the exception side, the submit method, because it returns a futureTask object, throws an exception from the thread when future.get() is invoked, so the caller can handle the exception easily. (If execute, only catch internally or set up catchHandler)

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

  • Shutdown: Stops receiving new tasks and waits for all existing tasks in the pool to complete (including threads in the waiting queue). Asynchronous methods, which return immediately after being called.
  • ShutdownNow: Stops receiving new tasks, stops all executing tasks, and returns a list of tasks that are still in the queue.
  • AwaitTermination: is simply a method to determine whether the current thread pool task is completely terminated. Usually used after shutdown, because shutdown is asynchronous and you need to know when it’s really over.

5. Thread state conversion

Q: The six states of A thread are:

  • New: A New thread is created, but start has not been called
  • RUNNABLE: Run. The ready state is included in the run state
  • It’s usually BLOCKED when you want to get the lock
  • WAITING: Usually after wait or join
  • TIMED_WAITING: timed waiting, that is to return after a fixed time, usually called sleep or wait(time).
  • TERMINATED: indicates the TERMINATED state.

Enjoy a good picture of which methods are called to get into which states.

Q: When does A Java thread block?

  • sleep
  • Wati () hangs waiting for the Notify () message sent by another thread
  • Waiting for the IO
  • Waiting for the lock

Six, Volatile

When volatile modifies a member variable, other threads see the change as soon as one thread modifies the variable.

Q: When a member variable is not volatile, why don’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 to main memory. This can cause one thread to change the value of a variable in main memory while another thread continues to use the value of its variable in the register.

Q: Does volatile eliminate the need for locking? A: No.

  • A lock does not guarantee only one variable’s mutual exclusion, sometimes it is to ensure that several members change continuously, so that other threads can not interfere with, read.
  • Volatile guarantees that one variable can be changed, but not atomicity when several variables change simultaneously.

Q: Show a classic example from the book “Java Concurrent Programming In Action”, which also appears in the subject 2 exam, but the example is changed. Why might the following example loop endlessly, or print 0?

A: first of all to understand Java reorder, can have A look at this post: www.cnblogs.com/coshaho/p/8…

Then analyze how the last two strange situations happened.

  • Never output: After ordering the program’s instructions, this occurs:
  1. ReaderThread reads the ready value in the while, which is false, and stores it in the ReaderThread register.
  2. The main thread changes ready and number.
  3. The ReaderThread does not sense changes to the READY register (there is no instruction for the ReaderThread to update the value of the READY register) and therefore enters an infinite loop.
  • 1) The main thread sets ready to true. 2) The ReaderThread reads ready in the while, which is true, and exits the while loop
  1. ReaderThread reads number, which is initialized to 0, and prints 0
  2. ReaderThread number=42; ReaderThread number=42;

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

7. Thread groups

To facilitate the management of a batch of threads, we use ThreadGroup to represent a ThreadGroup and manage a batch of threads by category

Usage:

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

ThreadGroup (Runnable) : ThreadGroup (Runnable) : ThreadGroup (Runnable) : ThreadGroup (Runnable

Q: Create thread B in thread A. Do they belong to the same thread group? A: Yes

Thread group is a bigger role for the same set of unified capture abnormal processing threads and avoid setUncaghtExceptionHandler again every time a new thread. 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 the code

If a thread throws an exception that is not caught inside the thread, what is the order in which the thread exception is handled? ThreadGroup = UncaughtException() {ThreadGroup = UncaughtException() {ThreadGroup = UncaughtException(); (2) if not, just to see if the Thread calls setUncaughtExceptionHandler () method to establish the Thread. SetUncaughtExceptionHandler instance. If it does, handle the exception directly with its UncaughtException() method. (3) If none of the above is true, see if the exception is an instance of ThreadDead. If so, do nothing. If not, print a printStackTrace.

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

Ok, don’t worry about memorizing, first look at the following question, ask what output: Q:

Static class GroupFather extends ThreadGroup {public GroupFather(String name) {super(name); 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) {// New GroupFather groupSon ("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 the code

A: groupFather = groupFather; groupFather = groupFather

Error, error, error, output is groupSon the sentence can be seen in many places, but people who have not read the source code will be misled by this sentence. In fact, the parent thread group does not refer to the thread group in the class inheritance relationship, but to the following:

That is to say, there is a parent-child relationship in the construction of relations. If the subclass threadGroup does not implement uncaughtException, the parent threadGroup specified in the constructor argument will be used to call the method.

Q: So I change it to the parent-child relationship on the constructional relationship. What is the output?

Public static void main(String[] args) {public static void main(String[] args) {// groupFather = new ThreadGroup("groupFather") {@override public void uncaughtException(Thread thread, Throwable throwable) { System.out.println("groupFather=" + throwable.getMessage()); }}; GroupSon = new ThreadGroup(groupFather, 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 the code

A: Answer output

That is, as long as the child thread group has been implemented, it will be used in the child thread group method, rather than directly to find the parent thread group!

Q: What if I let myself do the set catcher operation? So what’s the output here?

Public static void main(String[] args) {ThreadGroup group = new ThreadGroup("group") {@override public void main(String[] args) uncaughtException(Thread thread, Throwable throwable) { System.out.println("group=" + throwable.getMessage()); }}; Thread1 = new Thread(group, () -> {throw new RuntimeException(" I have an exception "); }); Thread1 setUncaughtExceptionHandler method. / / set themselves up setUncaughtExceptionHandler ((t, e) -> { System.out.println("no gourp:" + e.getMessage()); }); thread1.start(); }Copy the code

A: The thread group exception should be outputted. But the result is this:

That is to say, if the thread out setUncaughtExceptionHandler specially to oneself, then have priority handling UncaughtExceptionHandler do to set themselves up.

So is point (2) wrong? The second point actually refers to the default Thread catcher for global threads. ThreadGroup uncaughtException = ThreadGroup uncaughtException = ThreadGroup uncaughtException = ThreadGroup uncaughtException = ThreadGroup uncaughtException

The previous three points are explained here, but the code does not take into account that the thread itself sets the catcher

So revise the previous summary to summarize the actual exception throwing logic of the thread:

  1. If the thread itself has been setUncaughtExceptionHandler, then use your own set of press.

  2. If not, see that there are no thread groups. UncaughtException = uncaughtException = uncaughtException = uncaughtException = uncaughtException = uncaughtException = uncaughtException = uncaughtException

  3. If the Thread group and parent didn’t overwrite uncaughtException, by a Thread. Determine whether setDefaultUncaughtExceptionHandler (XXX) to set the global default trap, any global default is used

  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, the first time to learn about Huawei cloud fresh technology ~