1. Concurrency basis

Support for multithreading has been built into Java since its inception. When multiple threads are able to execute simultaneously, performance is significantly improved in most cases, especially in today’s multicore computers. However, multithreading in use also need to pay attention to many problems, if improperly used, will also cause a very serious impact on system performance.

1.1 Core concepts of concurrent programming

To understand concurrent programming, it is important to understand three concepts: atomicity, visibility, and orderliness.

1.1.1 atomic

Atomicity means that one or more operations are either all performed and performed without interruption by any factor, or none are performed at all

In atomic operations, essentially refused to multithreaded operations, both single-core and multi-core servers, when it comes to a certain data for atomic operation, at the same time only one thread to manipulate it, in simple terms, in the entire operation process won’t be interrupted by the thread scheduler, as an atomic operation is a = 1, but not an atomic operation +, A new Integer object is generated inside it.

For example, if you assign a 32-bit variable, the operation is divided into two steps: assign the lower 16 bits and assign the higher 16 bits. When thread A succeeded in writing data to the lower 16 bits, thread A was interrupted. When another thread B reads the value of A, it reads the wrong data.

Atomic operations in Java include:

  1. Basic types of read and assignment operations, and assignments must be numeric to variables, not atomic to each other

  2. Assignment for all references

  3. All operations of all Atomic operations classes in the java.concurrent.atomic.* package.

1.1.2 visibility

Visibility: When multiple threads access the same shared variable, when one thread changes the value of the shared variable, other threads can immediately see the changed value.

In a multi-threaded environment, the operation of one thread on a shared variable is not visible to other threads by default, that is, the modification of a shared variable by one thread cannot be viewed by other threads by default. For visibility, Java’s volatile, synchronized, and lock all guarantee visibility. If a variable is volatile, it is immediately updated to memory when a thread changes the shared variable, and is read directly from main memory when other threads read the shared variable. Synchronized and lock ensure that only one thread at a time acquires the lock and executes the synchronization code, flushing changes to main memory before releasing the lock. So visibility is guaranteed.

public static void main(String[] args) throws InterruptedException { new Thread(new Runnable() { @Override public void Run () {system.out.println (thread.currentThread ().getName()+" Thread started "); While (true) {// if(a==3) {break; }} system.out.println (thread.currentThread ().getName()+" Thread stopped "); } },"t2").start(); @override public void run() {system.out.println (thread.currentThread ().getName()+" Thread started "); While (true) {// if(a==3) {break; }} system.out.println (thread.currentThread ().getName()+" Thread stopped "); } },"t1").start(); Thread.sleep(1000); // Change the variable in the main thread, test whether other threads can see the change a=3; }Copy the code

Through the program is running as a result, can be found without the variable volatile keyword, thread 1 and thread 2 will into an infinite loop, because in the main thread to modify a, thread thread 1 and 2 is unable to perceive, and for variable a joined the volatile keyword, is not infinite loop, because it can guarantee the visibility of the thread.

1.1.3 order

Orderliness: the order in which the program is executed is the order in which the code is executed.

It can be understood that all operations in this thread are in order. If you look at thread B in thread A, all the operations are unordered. The JMM (Java Memory Model) allows the compiler and processor to rearrange instructions to improve program execution efficiency. Instruction reordering is not a problem for a single thread, but not for multiple threads (singleton double-checking is the solution to this instruction reordering problem).

In Java, synchronized and lock can be used to ensure order. Synchronized and lock ensure that there is a thread executing synchronized code at any time, which is equivalent to the sequential execution of synchronized code by threads, which naturally ensures order.

Order can also be guaranteed by using volatile. The most famous example is the singleton DCL (double-checked lock)

public class Singleton { private volatile static Singleton instance; public static Singleton getInstance(){ if (instance == null){ synchronized (Singleton.class){ if (null == instance){ instance = new Singleton(); } } } return instance; }}Copy the code

For concurrent programming, to ensure the correctness of the program, for atomicity, visibility, order is very important!!

1.2 Process Threads

1.2.1 What is a Process

A process can be thought of as a startup instance of an application. Such as wechat, IDEA, Navicat, etc., when opening them, it is equivalent to starting a process. Each process has its own memory space, address, file resources, data resources, etc. Processes are the smallest unit of resource allocation and management

1.2.2 What is a thread

Threads are subordinate to the process and are the actual executor of the program. A process can contain several threads, and threads can also be called lightweight processes. Each thread has its own counters, stacks, local variables, and so on. And access to shared memory variables. A thread is the smallest unit of operating system (CPU) scheduling and execution. The CPU switches back and forth between these threads (context switches), giving the user the impression that the threads are executing synchronously.

1.2.3 Problems caused by thread Usage

There is a misconception that using multiple threads in your code will definitely improve your system’s performance. The purpose of concurrent programming is to make the program run faster, but it does not mean that the more threads start, the greater the performance improvement, it will be affected by many factors, such as locking problems, thread state switching problems, thread context switching problems, but also by hardware resources, such as the number of CPU cores.

1.2.3.1 What is context switching for threads

In both multi-core and single-core processors, code can be executed in multithreaded form, and the CPU allocates CPU time slices to each thread to achieve fast switching during thread execution. The so-called time slice is the execution time allocated by CPU to each thread. When a thread obtains the CPU time slice, it will execute within a certain time. When the time slice expires, the thread will enter the suspended state. The time slice is usually tens of milliseconds, and the user feels that it is executed synchronously through high-speed switching in the CPU.

At the same time, it is important to keep track of which commands and variable values were executed when the thread was suspended during the switch, which is guaranteed by the program counter inside each thread.

Simply put: a thread is a context switch from suspension to loading. It’s more resource-intensive

Several situations that cause context switches;

  1. When the time slice is used up, the CPU schedules the next task.
  2. It is preempted by other tasks of higher priority
  3. When I/O blocks during task execution, the scheduler suspends the current task and switches to the next task
  4. User code actively suspends the current task to free up CPU time
  5. Multi-task preempt resource, suspended because it was not grabbed
  6. Hardware interrupt

1.3CPU time slice rotation mechanism & Optimization

As mentioned earlier, thread execution is dependent on the amount of time the CPU allocates to each thread. In CPU slice rotation, if a thread’s slice expires, the CPU suspends that thread and allocates a slice to another thread. If the process blocks or ends before the time slice ends, the CPU switches immediately

Too short a time slice can lead to frequent process switches, reducing CPU efficiency, while too long can lead to poor response to short interaction requests. A time slice of 100ms is usually a reasonable compromise.

1.4 Understanding of parallelism and concurrency

Concurrency is the execution of multiple tasks alternately, usually with a time unit, which is the number of concurrent tasks in a unit of time.

Parallelism is the ability to execute multiple tasks simultaneously. For example, you can use the bathroom while playing with your phone.

1.5 Starting and terminating threads

Threads can be implemented in two ways: inheriting the Thread class and implementing the Runnable interface. Some books say implement Callable interface. But defining threads through this interface is not the way Java standards define them, but rather based on the idea of a Future.

What’s the difference between Thread and Runnable? In general, a Thread is an abstraction of a Thread, while a Runnable is an abstraction of business logic, and a Thread can take any instance of Runnable and execute it.

1.5.1 Starting a Thread

Public class UseThread extends Thread{private static class UseThread extends Thread{@override public void run() { System.out.println(Thread.currentThread().getName()+": use thread"); } } private static class UseRunnable implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+": use runnable"); } } public static void main(String[] args) { System.out.println(Thread.currentThread().getName()+": use main"); UseThread useThread = new UseThread(); useThread.start(); Thread thread = new Thread(new UseRunnable()); thread.start(); }}Copy the code

Optimization: Before starting a thread, it is best to set a specific thread name for the thread so that when problems occur, the developer can quickly locate the problem

1.6.2 Thread Termination

A thread normally terminates when run completes or an exception occurs

Suspend () Resume () stop ()

These three methods correspond to pause, resume, and end. The results of these three methods are shown below:

public class Srs { private static class MyThread implements Runnable{ @Override public void run() { DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); while (true){ System.out.println(Thread.currentThread().getName()+"run at"+dateFormat.format(new Date())); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new MyThread());  System.out.println(" start thread "); thread.start(); TimeUnit.SECONDS.sleep(3); // Suspend thread system.out.println (" suspend thread "); thread.suspend(); TimeUnit.SECONDS.sleep(3); System.out.println(" restore thread "); thread.resume(); TimeUnit.SECONDS.sleep(3); // Abort thread system.out.println (" abort thread "); thread.stop(); }}Copy the code

Execution Result:

Can see these three methods, very good to complete the work. But these three methods are already marked as expired in the Java source code. Why?

When suspend() is called, the thread does not release the resource it currently holds (such as a lock), but rather enters a suspended state holding the resource, which can cause deadlock problems.

public class Srs { private static Object obj = new Object(); Private static class MyThread implements Runnable{@override public void run() {synchronized (obj){DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); while (true){ System.out.println(Thread.currentThread().getName()+"run at"+dateFormat.format(new Date())); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } } } public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new MyThread()," normal thread "); Thread1 = new Thread(new MyThread()," deadlocked Thread "); // Start the thread thread.start(); TimeUnit.SECONDS.sleep(3); Thread.suspend (); System.out.println(" suspend thread "); thread1.start(); TimeUnit.SECONDS.sleep(3); }}Copy the code

In the code above, the normal thread holds the lock, and when the suspend method is called, because it does not release the lock, the thread is deadlocked and cannot execute because the lock cannot be obtained.

When stop() is called, the remaining operations in run() are immediately stopped. Therefore, some work may not be completed, such as file streams, databases, etc. In addition, all locks held by the thread are immediately released. As a result, data cannot be processed synchronously and data inconsistency occurs.

public class StopProblem { public static void main(String[] args) throws Exception { TestObject testObject = new TestObject(); Thread t1 = new Thread(() -> { try { testObject.print("1", "2"); } catch (Exception e) { e.printStackTrace(); }}); t1.start(); Thread.sleep(1000); t1.stop(); System.out.println("first : " + testObject.getFirst() + " " + "second : " + testObject.getSecond()); } } class TestObject { private String first = "ja"; private String second = "va"; public synchronized void print(String first, String second) throws Exception { System.out.println(Thread.currentThread().getName()); this.first = first; Timeunit.seconds.sleep (3); this.second = second; } public String getFirst() { return first; } public String getSecond() { return second; }}Copy the code

Output result:

1.6.2.2 Thread abort safe and graceful posture

Java designs an interrupt property for thread-safe abort, which can be understood as an identifying bit property of the thread. It is used to indicate whether a running thread has been interrupted by another thread. It’s like another thread says hello to this thread and tells it it’s time to stop. This is done with interrupt().

public class InterruptDemo { private static class MyThread implements Runnable{ @Override public void run() { while (true){ System.out.println(Thread.currentThread().getName()+" is running"); } } } public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new MyThread(),"myThread"); thread.start(); TimeUnit.SECONDS.sleep(3); thread.interrupt(); }}Copy the code

After adding this method, an exception is raised, but you can see that the thread does not continue execution.

A thread responds by checking whether or not it has been interrupted, using isInterrupted(), which returns true for interruption or false for no interruption. It allows you to interrupt a thread.

public class InterruptDemo {


    private static class MyThread implements Runnable{

        @Override
        public void run() {

            //while (true){
            while (!Thread.currentThread().isInterrupted()){

                System.out.println(Thread.currentThread().getName()+" is running");
            }

            System.out.println(Thread.currentThread().getName()+" Interrupt flag is : "+Thread.currentThread().isInterrupted());
        }
    }

    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(new MyThread(),"myThread");

        thread.start();

        TimeUnit.SECONDS.sleep(3);

        thread.interrupt();
    }
}
Copy the code

The thread interrupt attribute can be used to perform interrupt operations.

Threads can also use the static thread.interrupted () method to check whether a Thread has been interrupted and reset its interrupt identifier. If this method is used, the Thread’s interrupt identifier is changed from true to false.

public class InterruptDemo { private static class MyThread implements Runnable{ @Override public void run() { //while (true){ //while (! Thread.currentThread().isInterrupted()){ while (! Thread.interrupted()){ System.out.println(Thread.currentThread().getName()+" is running"); } System.out.println(Thread.currentThread().getName()+" Interrupt flag is : "+Thread.currentThread().isInterrupted()); } } public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new MyThread(),"myThread"); thread.start(); TimeUnit.SECONDS.sleep(3); thread.interrupt(); }}Copy the code

Also note that a thread in a deadlock cannot be interrupted

1.7 Common methods of deep-thread operations

1.7.1 Understand the run() and start() methods

Both methods can start threads, but they are fundamentally different. A thread is actually started when it executes the start() method, which puts a thread into a ready state waiting for a CPU slice to be allocated, and then calls run(). Note that the start() method of the same thread cannot be called twice, otherwise an exception will occur. If the start() method is called twice, the thread state will not be new, and threadStatus will not be equal to 0.

Public synchronized void start() {public synchronized void start() {public synchronized void start() { The creation of a local thread calls the same method used by the current system to create a thread. Finally, run() is called to actually execute the thread. The 0 state is equal to the 'New' state. */ if (threadStatus ! = 0) throw new IllegalThreadStateException(); /* The thread is added to the thread group, and then start0() */ group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (! started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } }}Copy the code

Run (), on the other hand, is just a normal method, with the same meaning as a member method in a class. Thread-executed business logic can be implemented in this method. The thread is not started asynchronously, in other words, a new thread is not started. It can be executed separately or repeatedly.

1.7.2 wait (), notify ()

Wait (), notify(), and notifyAll() are three methods defined in the Object class that control the state of threads.

Note: It must be used in thread synchronization and is the resource of the same lock

Examples of wait and notify methods that turn on and off:

public class WaitNotify { static boolean flag = false; static Object lock = new Object(); Static class thread implements Runnable{@override public void run() {synchronized (lock){static class thread implements Runnable{@override public void run() {synchronized (lock){while (! System.out.println(thread.currentThread ().getName()+" flag is false,waiting"); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); System.out.println(thread.currentThread ().getName()+" flag is true"); } } } static class NotifyThread implements Runnable{ @Override public void run() { synchronized (lock){ System.out.println(Thread.currentThread().getName()+" hold lock"); lock.notify(); flag=true; try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) throws InterruptedException { new Thread(new WaitThread(),"wait").start(); TimeUnit.SECONDS.sleep(1); new Thread(new NotifyThread(),"notify").start(); }}Copy the code

1) The object lock is acquired by a WaitThread. 2) A WaitThread calls the wait() method of the object to waive the lock and enter the WaitQueue. 3) The lock is released by a WaitThread. 4) After NotifyThread successfully obtains the lock, notify() or notifyAll() is called to move WaitThread from WaitQueue to SynchronizedQueue. At this point, WaitThread is blocked. 5) After NotifyThread releases the lock, WaitThread acquires the lock again and continues execution from wait

1.7.3.1 Wait notification paradigm

Multithreaded waiting for notification is a very common interview question, often seen in written tests. For waiting notifications, there needs to be producers (notifiers) and consumers (waiters)

Wait for the party:

  1. Obtaining the object lock
  2. If the condition is not met, the object’s wait() method is called and the condition is still checked after being notified
  3. If the conditions are met, the corresponding logic is executed
Synchronized (object){while(condition not satisfied){object.wait (); } If the conditions are met, the service logic is executed. }Copy the code

Notify party:

  1. Get the object lock.
  2. Change the conditions
  3. Notifies the threads waiting on the object
Synchronized (object){change condition objects. notify()}Copy the code

1.7.4 Differences between wait and sleep

The first thing to know about the sleep method is that it belongs to the Thread class, and the wait() method belongs to the Obejct class

The sleep() method causes the program to pause execution for the specified time, allowing the CPU to schedule another thread, but its monitoring state remains the same and resumes when the specified time is up.

Wait () relinquishes control and enters the lock pool waiting for the object. Only after notify() is called does the thread enter the lock pool to acquire the lock and run the object

The thread does not release the lock during the call to sleep. When wait() is called, the thread releases the lock.

1.7.5 understand yield ()

When a thread calls this method, the thread immediately frees its own time slice. The thread will enter the ready state, and the CPU will select another thread to fragment, but note that the thread that called this method may be selected again by the CPU to execute.

This method does not release the lock. If you need to release the lock, you can do it yourself before calling the method.

public class YieldDemo { static class MyThread implements Runnable{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+" : "+i); if (i == 5){ System.out.println(Thread.currentThread().getName()); Thread.yield(); } } } } public static void main(String[] args) { new Thread(new MyThread()).start(); new Thread(new MyThread()).start(); new Thread(new MyThread()).start(); }}Copy the code

The result shows that when this method is called, the thread will give up its time fragment, but it may be selected to execute again.

Thread-3 0
Thread-1 0
Thread-5 0
Thread-5 1
Thread-5 2
Thread-5 3
Thread-5 4
Thread-5 5
Thread-5
Thread-1 1
Thread-1 2
Thread-1 3
Thread-1 4
Thread-1 5
Thread-1
Thread-1 6
Thread-1 7
Thread-1 8
Thread-1 9
Thread-3 1
Thread-3 2
Thread-3 3
Thread-3 4
Thread-3 5
Thread-3
Thread-5 6
Thread-5 7
Thread-5 8
Thread-5 9
Thread-3 6
Thread-3 7
Thread-3 8
Thread-3 9
Copy the code

1.7.6 understand the join ()

The use of this method, in the actual development, the application is relatively little. But in the interview, often accompanied by a question, how to ensure the order of execution of threads? Can be set using this method.

1.7.6.1 use

When a thread calls this method, its state changes from ready to run.

public class JoinDemo { private static class MyThread extends Thread{ int i; Thread previousThread; Public MyThread(Thread previousThread,int I){this.previousThread=previousThread; this.i=i; } @override public void run() {// Call the join method of the previous thread. // previousthread.join (); System.out.println("num:"+i); } } public static void main(String[] args) { Thread previousThread=Thread.currentThread(); for(int i=0; i<10; I++){// each thread implementation holds a reference to the previous thread. MyThread joinDemo=new MyThread(previousThread,i); joinDemo.start(); previousThread=joinDemo; }}}Copy the code
num:0
num:2
num:3
num:6
num:7
num:1
num:5
num:9
num:8
num:4
Copy the code

After the join is enabled, the result is ordered.

num:0
num:1
num:2
num:3
num:4
num:5
num:6
num:7
num:8
num:9
Copy the code

As you can see from the result, the current thread must wait for the previousThread to terminate before returning from thread.join. The thread waits at the join.

1.7.6.2 Mechanism Analysis

public final void join() throws InterruptedException { join(0); }... public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); If (millis == 0) {//isAlive retrieves thread status, While (isAlive()) {// Call Object's wait method to block wait(0); }} else {// block until timeout while (isAlive()) {long delay = millis-now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; }}}Copy the code

You can see that this method is modified by synchronized because, internally, thread blocking is implemented through the wait method in Object, and synchronized must be added to call wait().

To sum up, Thread.join uses wait/notifyall to communicate with threads to block. When a thread finishes executing, two things are triggered. The first is to set the native thread object to NULL, and the second is to wake up the wait method on the previousThread lock using notifyall.

1.8 Thread Priority

Operations for threads are invoked through CPU time slices, so the amount of time slices a thread is allocated determines how much resources it uses, and thread priority is a thread attribute that determines how much resources a thread needs to be able to use.

The priority of a thread ranges from 0 to 10. The default priority of a thread is 5, which can be changed by setPriority() when a thread is built. A thread with a higher priority allocates more time slices than a thread with a lower priority.

In general, threads that block frequently need to be set to a higher priority, while threads that are computationally heavy need to be set to a lower priority to ensure that the processor is not monopolized.

Note, however, that thread priority cannot be used as a dependency on thread execution correctness, because different operating systems may ignore setting priority.

1.9 Daemon Threads

Daemon threads are a supported type of thread, and the threads we created earlier can be called user threads. Daemon threads allow you to do support work such as GC and distributed lock renewals. The daemon thread ends with the end of the user.

For daemon thread creation, setDaemon() can be set.

public class DaemonDemo { private static class MyThread implements Runnable{ @Override public void run() { while (true){  System.out.println(Thread.currentThread().getName()); } } } public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new MyThread()); // Set thread.setdaemon (true); thread.start(); TimeUnit.SECONDS.sleep(2); }}Copy the code

When a thread instance is not set as a daemon thread, the thread does not terminate with the main thread. However, when set as a daemon thread, when the main thread terminates, the thread terminates with it. At the same time, the daemon thread does not necessarily execute the finally code block. Therefore, when a thread is set as a daemon thread, there is no guarantee that operations such as resource cleaning will be performed.

1.10 Thread Status