Deep understanding of Java multithreading and concurrent programming

  • 2021-07-09 08:26:28
  • OfStack

1. Three features of multithreading

Multithreading has three characteristics: atomicity, visibility and order.

Atomicity

(Similar to atomicity in the transactional nature of a database, the atomicity of a database is reflected in the need to commit after the dml statement is executed):
Understanding: That is, one or more operations, either all of them will be executed and will not be interrupted by any factors in the process of execution, or none of them will be executed.
A classic example is the bank account transfer problem:
For example, transferring 1000 yuan from account A to account B must include two operations: subtracting 1000 yuan from account A and adding 1000 yuan to account B. These two operations must be atomic to ensure that there are no unexpected problems.
The same is true of our manipulation data, such as i = i+1; It includes reading the value of i, calculating i and writing i. This line of code is not atomic in Java, so multithreading will definitely go wrong, so we need to use synchronous synchronized and lock locks to ensure this feature.
Atomicity is actually to ensure that data 1 is safe and thread 1 is safe.

Visibility:

Visibility is closely related to java memory model.
When multiple threads access the same variable, one thread modifies the value of the variable, and the other threads can immediately see the modified value.
If two threads are in different cpu, then thread 1 changes the value of i and has not refreshed it to main memory, and thread 2 uses i again, then this i value must be the same as before, and thread 1 does not see the modification of variables by thread 2, which is a visibility problem.

Orderliness:

Understanding: The sequence of program execution is executed according to the sequence of code.
Generally speaking, in order to improve the efficiency of program operation, the processor may optimize the input code. It does not guarantee that the execution sequence of each statement in the program is the same as that in the code, but it will guarantee that the final execution result of the program and the execution result of the code sequence are 1.

For example:


int a = 10; // Statement 1
int r = 2; // Statement 2
a = a + 3; // Statement 3
r = a*a;  // Statement 4

Because of reordering, he may also execute the order 2-1-3-4, 1-3-2-4
But it's impossible to be 2-1-4-3, because it breaks the dependency.
Obviously, reordering is not a problem for single-threaded operation, while multithreaded operation is not fixed, so we have to consider this problem when programming multithreaded.
Method to ensure order in multithreading: join ()

2. Java memory model

The memory structure of jvm is heap, stack and method area, which is different from the memory model of java. The memory model of Java is related to multithreading.

Understanding: The shared memory model refers to the Java memory model (JMM for short). When JMM decides that one thread can write shared variables, it can be visible to another thread. From an abstract point of view, JMM defines an abstract relationship between threads and main memory: shared variables between threads are stored in main memory (main memory) (local variables are not stored), and each thread has one private local memory (local memory), which stores the thread to read/write copies of the shared variables. Local memory is an abstract concept of JMM, which does not really exist. It covers caching, write buffers, registers, and other hardware and editor optimizations.

Summary: What is the Java memory model: The java memory model is simply called jmm, which defines that one thread is visible to another thread. Shared variables are stored in the main memory, and each thread has its own local memory. When multiple threads access one data at the same time, the local memory may not be refreshed to the main memory in time, so thread safety problems will occur.

3. Volatile keyword

What the Volatile keyword does: Variables are visible across multiple threads.

The Volatile keyword is non-atomic and cannot guarantee the atomicity of data. It can only refresh the solution to the main memory immediately and cannot solve the concurrency problem.

If you want to ensure the atomicity of data and solve the concurrency problem, you need to use the AutomicInteger atomic class in the concurrent package.

The difference between volatile and synchronized:
volatile alone cannot guarantee the safety (atomicity) of threads.

1. volatile is lightweight and can only decorate variables. synchronized heavyweight, can also be modified method. 2. volatile only guarantees data visibility and cannot be used for synchronization, because concurrent access to volatile-decorated variables by multiple threads does not block.

synchronized guarantees not only visibility, but also atomicity, because only the thread that has acquired the lock can enter the critical section, thus ensuring that all statements in the critical section are executed. Blocking occurs when multiple threads compete for synchronized lock objects.

synchronized locks the shared variables in main memory, and only one thread will always operate the shared variables in main memory.

Thread safety includes two conveniences: 1. Visibility 2. Atomicity

While volatile alone does not guarantee thread safety, synchronized does.

Code implementation:


package chauncy.concurrentprogramming;

class ThreadVolatile extends Thread {
	public volatile boolean flag = true;

	@Override
	public void run() {
		System.out.println(" The child thread starts executing ...");
		while (flag) {

		}
		System.out.println(" The child thread ends execution ...");
	}

	public void isRun(boolean flag) {
		this.flag = flag;
	}
}

/**
 * @classDesc:  Functional description (Volatile Use of keywords )
 * @author: ChauncyWang
 * @createTime: 2019 Year 3 Month 12 Day   Morning 10:17:14
 * @version: 1.0
 */
public class Volatile {
	public static void main(String[] args) throws InterruptedException {
		ThreadVolatile threadVolatile1 = new ThreadVolatile();
		threadVolatile1.start();
		Thread.sleep(300);
		/**
		 *  If you do not add the variable Volatile Keyword, the child thread will not stop running   Reason: The threads are not visible between them, and the copy is read, and the main memory results are not read in time. 
		 *  Solution: Use Volatile Keyword solves the visibility between threads, forcing threads to take the value in "main memory" every time they read the value. 
		 */
		threadVolatile1.isRun(false);
		System.out.println("flag:" + threadVolatile1.flag);
	}
}

package chauncy.concurrentprogramming;

import java.util.concurrent.atomic.AtomicInteger;

class VolatileNoAtomicThread extends Thread {
	// private static volatile int count = 0;
	private static AtomicInteger atomicInteger = new AtomicInteger(0);

	@Override
	public void run() {
		for (int i = 0; i < 1000; i++) {
			// count++;
			atomicInteger.incrementAndGet();// count++
		}
		System.out.println(getName() + "-----" + atomicInteger);
	}
}

/**
 * @classDesc:  Functional description (Volatile Modification is not atomic ( Not synchronized ) Can't solve the thread safety problem )
 * @author: ChauncyWang
 * @createTime: 2019 Year 3 Month 12 Day   Morning 10:39:30
 * @version: 1.0
 */
public class VolatileNoAtomic {
	public static void main(String[] args) {
		//  Initialization 10 Threads 
		VolatileNoAtomicThread[] volatileNoAtomicThread = new VolatileNoAtomicThread[10];
		for (int i = 0; i < volatileNoAtomicThread.length; i++) {
			//  Create every 1 Threads 
			volatileNoAtomicThread[i] = new VolatileNoAtomicThread();
		}
		for (int i = 0; i < volatileNoAtomicThread.length; i++) {
			//  Start every 1 Threads 
			volatileNoAtomicThread[i].start();
		}
	}
}

4. TreadLocal

1. What is ThreadLocal?

ThreadLocal increases the local variables of 1 thread, accessing a thread with its own local variables.

When using ThreadLocal to maintain a variable, ThreadLocal provides an independent copy of the variable for each thread using the variable, so every thread can change its own copy independently without affecting the corresponding copies of other threads.

There are four ThreadLocal interface methods:

void set (Object value) sets the value of the thread local variable for the current thread; public Object get () This method returns the thread local variable corresponding to the current thread; public void remove () deletes the value of the local variable of the current thread in order to reduce memory usage, which is a new method in JDK 5.0. It should be pointed out that when the thread ends, the local variables corresponding to the thread will be automatically garbage collected, so it is not necessary to explicitly call this method to clear the local variables of the thread, but it can speed up the memory collection; protected Object initialValue () returns the initial value of the local variable for this thread, a method of protected, apparently designed for subclass override. This method is a delayed invocation method that executes only once when the thread calls get () or set (Object) for the first time. The default implementation in ThreadLocal directly returns 1 null.

2. The underlying implementation principle of ThreadLocal:

ThreadLocal via Thread. currentThread (); Gets the current thread

Operation map Set: ThreadLocalMap

void set (Object value) is Map. put ("current thread", value);

public Object get () is to get ThreadLocalMap and then return.

Code implementation:


package chauncy.concurrentprogramming;

class Res {
	// private int count=0;
	/*
	 *  Set local local variables, which are isolated from other thread local variables and have no influence on each other 
	 */
	private ThreadLocal<Integer> count = new ThreadLocal<Integer>() {
		protected Integer initialValue() {
			//  Sets the initialization value of the local variable of the current thread 
			return 0;
		};
	};

	/**
	 * 
	 * @methodDesc:  Functional description ( Generate order number )
	 * @author: ChauncyWang
	 * @param: @return
	 * @createTime: 2019 Year 3 Month 12 Day   Afternoon 2:23:57
	 * @returnType: int
	 */
	public Integer getNum() {
		int count = this.count.get() + 1;
		this.count.set(count);
		return count;
	}
}

class ThreadLocalDemo extends Thread {
	private Res res;

	public ThreadLocalDemo(Res res) {
		this.res = res;
	}

	@Override
	public void run() {
		for (int i = 0; i < 3; i++) {
			try {
				Thread.sleep(30);
			} catch (Exception e) {
			}
			System.out.println(getName() + "----i:" + i + ",number:" + res.getNum());
		}
	}
}

/**
 * @classDesc:  Functional description ( Use of local threads: creating 3 Threads, each generating its own independent sequence number )
 * @author: ChauncyWang
 * @createTime: 2019 Year 3 Month 12 Day   Afternoon 2:21:03
 * @version: 1.0
 */
public class ThreadLocalTest {
	public static void main(String[] args) {
		Res res = new Res();
		ThreadLocalDemo t1 = new ThreadLocalDemo(res);
		ThreadLocalDemo t2 = new ThreadLocalDemo(res);
		ThreadLocalDemo t3 = new ThreadLocalDemo(res);
		t1.start();
		t2.start();
		t3.start();
	}
}

5. Thread pool

1. Why use thread pools?

Because it is very resource-intensive to start or stop one thread to manage threads through the thread pool, it can save memory to hand over threads to the thread pool for management.
In general, we all use thread pool in enterprise development, and integrate thread pool and asynchronous annotation through spring.

2. What is a thread pool?

Thread pool is to create a collection of threads during initialization of a multithreaded application, and then reuse these threads instead of creating a new one when new tasks need to be performed. The number of threads in the thread pool usually depends entirely on the amount of available memory and the requirements of the application. However, it is possible to increase the number of available threads. Each thread in the thread pool is assigned a task. Once the task is completed, the thread returns to the pool and waits for the next task assignment.

3. Thread pool function:

The use of thread pools in multithreaded applications is necessary for several reasons:

1. Thread pooling improves the corresponding time for 1 application. Because the threads in the thread pool are ready and waiting to be assigned tasks, the application can use them directly without creating a new thread. 2. Thread pooling saves CLR the overhead of creating one complete thread for each short-life task and can recycle resources after the task is completed. 3. The thread pool optimizes the thread time slice according to the process currently running in the system. 4. Thread pooling allows us to open multiple tasks without setting properties for each thread. 5. Thread pooling allows us to pass an object reference containing state information for the program parameter that is executing the task. 6. Thread pool can be used to solve the problem of limiting the maximum number of threads to handle a specific request.

4. Four ways to create a thread pool:

java provides four thread pools through Executors (a concurrent package of jdk 1.5), which are:

1. newCachedThreadPool creates a cacheable thread pool. If the length of the thread pool exceeds the processing needs, it can flexibly recycle idle threads, and if there is no recycle, it can create new threads. 2. newFixedThreadPool creates a fixed-length thread pool, which can control the maximum concurrency of threads, and the excess threads will wait in the queue. 3. newScheduledThreadPool creates a fixed-length thread pool to support timed and periodic task execution 4. newSingleThreadExecutor creates a single-threaded thread pool, which only uses 1 worker thread to execute tasks, and ensures that all tasks are executed in the specified order (FIFO, LIFO, priority). (1 will not be used)

Summary: The thread pool created by newCachedThreadPool is infinite. When the first task is completed when the second task is executed, the thread executing the first task will be reused instead of creating a new thread every time. newFixedThreadPool executes several threads per pass-in parameter size, with other threads waiting (not much used in the enterprise). newScheduledThreadPool uses the schedule method to create a pool of delayed threads per unit time.

Code implementation:


package chauncy.concurrentprogramming.executors;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class NewCachedThreadPool {
	public static void main(String[] args) {
		//  Create a cacheable thread pool 
		ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
		//  Execute execute Method represents the creation of a 1 Threads, similar to start
		for (int i = 0; i < 30; i++) {
			int index = i;
			// index++;
			newCachedThreadPool.execute(new Runnable() {

				@Override
				public void run() {
					try {
						Thread.sleep(300);
					} catch (InterruptedException e) {
					}
					//  That is used in the inner class i Must be final, But instead index Don't report an error after that, because jdk1.8 It is optimized and can identify index Whether it has been changed, if it is changed int
					// index=i; Below index++ If you let go, you will report an error. 
					System.out.println(Thread.currentThread().getName() + "----" + index);
				}
			});
		}
		//  Close the thread pool 
		newCachedThreadPool.shutdown();
	}
}

package chauncy.concurrentprogramming.executors;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class NewFixedThreadPool {
	public static void main(String[] args) {
		// newFixedThreadPool  It can only be executed at most at a time 3 Other threads are waiting to execute. 
		ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(3);
		for (int i = 0; i < 10; i++) {
			int index = i;
			newFixedThreadPool.execute(new Runnable() {
				public void run() {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
					}
					System.out.println(Thread.currentThread().getName() + "----i:" + index);
				}
			});
		}
	}
}

package chauncy.concurrentprogramming.executors;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class NewScheduledThreadPool {
	public static void main(String[] args) {
		//  The input parameter is the thread pool size, 
		ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5);
		// schedule Thread pool for executing timed tasks , No. 1 1 Parameters need to be created Runnable Interface object, the 2. 3 How many unit time executions are indicated by a parameter run Method. 
		newScheduledThreadPool.schedule(new Runnable() {
			public void run() {
				System.out.println(" I am 3 Second. . . . ");
			}
		}, 3, TimeUnit.SECONDS);
	}
}

package chauncy.concurrentprogramming.executors;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class NewSingleThreadExecutor {
	public static void main(String[] args) {
		ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
		for (int i = 0; i < 10; i++) {
			int index = i;
			newSingleThreadExecutor.execute(new Runnable() {
				public void run() {
					System.out.println(Thread.currentThread().getName() + "----i:" + index);
				}
			});
		}
	}
}

Related articles: