JUC

I. Introduction to JUC

In Java 5.0, the java.util.Concurrent (JUC) package adds utility classes commonly used in concurrent programming to define custom thread-like subsystems, including thread pools, asynchronous IO, and lightweight task frameworks. Provides adjustable, flexible thread pools. Collection implementations designed for use in multithreaded contexts are also provided.

Two, multithreading

1. Threads and processes

A program is a collection of instructions written in a language to accomplish a specific task. A static piece of code.

A process, “process,” is an execution of a program, or a running program. Process is a dynamic process, that is, a process with its own creation, existence and extinction. Every Java program has an implicit main program, the main method.

A thread is a specific execution path within a process. Multithreading is supported if a program can execute more than one thread at a time.

Conclusion: the program is static, the program runs into a process, a process can have multiple threads at the same time. A process is a collection of all threads, and each thread is an execution path in the process.

Concurrency vs. parallelism:

You’re in the middle of a meal when the phone comes and you don’t answer it until after you’ve finished, which means you don’t support concurrency or parallelism. You’re in the middle of a meal when the phone call comes, you stop to answer the phone, and then continue eating, which means you support concurrency. You’re in the middle of a meal when the phone call comes and you’re eating while you’re talking, which means you support parallelism.

The key to concurrency is that you have the ability to handle multiple tasks, not necessarily all at once. The key to parallelism is your ability to handle multiple tasks at once.

What matters most is whether they are “simultaneously”.

2. Advantages of multi-threading

① Improve application responsiveness. It makes more sense for graphical interfaces and enhances the user experience.

② Improve the utilization rate of CPU in computer system

③ Improve the program structure. Long and complex processes are divided into multiple threads and run independently, making it easier to understand and modify

④ Using threads can put time-consuming tasks in the background to deal with, such as waiting for user input, file reading and writing, and network data receiving and receiving.

3. Thread safety

3.1 Definition of thread safety

When multiple threads share the same global or static variable at the same time, data collisions can occur, which is also known as thread-safety problems. However, data conflicts do not occur when performing read operations.

3.2 Thread-safe solution

Using synchronization between multiple threads or using locks can solve thread-safety problems. The core of this is that there will be a data collision problem (thread insecurity) and only one thread will be allowed to execute. Release the lock after code execution is complete before letting other threads execute. This solves the thread insecurity problem.

3.3 What is synchronization between multiple threads

In an environment where multiple threads share the same resource, each thread can work without interference from other threads, which is called synchronization between multiple threads.

4. Address thread safety

4.1 Use synchronization code

synchronized(same object){thread collisions may occur}Copy the code

** Note: ** In a synchronized code block, multiple threads must use the same lock, i.e. the same object.

Normally, in a thread class implemented with Runnable, we would use this as the lock object.

In Thread classes that use Thread inheritance, their Class objects are typically used (Class objects are created only once in the JVM).

4.2 Using the Synchronization method

class Ticket {
	private int number = 400;

	// 1
	public synchronized void sale(a) {
		// 2. Synchronize code blocks
		synchronized (this) {}if (number > 0) {
			System.out.println(Thread.currentThread().getName() + "Selling the first" + (number--) + "One ticket \t, still left"+ number); }}public void sale1(a) {
		Lock lock = new ReentrantLock();
		lock.lock();
		try {
			if (number > 0) {
			System.out.println(Thread.currentThread().getName() + "Selling the first" + (number--) + "One ticket left."+ number); }}finally{ lock.unlock(); }}}/ * * * *@DescriptionThree ticket sellers sell thirty tickets
public class SaleTicket {
	public static void main(String[] args) {
		Ticket tk = new Ticket();
// new Thread(()->{for(int i = 0; i<30; i++) tk.sale(); }, "AA").start();
// new Thread(()->{for(int i = 0; i<30; i++) tk.sale(); }, "BB").start();
// new Thread(()->{for(int i = 0; i<30; i++) tk.sale(); }, "CC").start();
		new Thread(()->{for(int i = 0; i<30; i++) tk.sale1(); },"AA").start(); 
		new Thread(()->{for(int i = 0; i<30; i++) tk.sale1(); },"BB").start(); 
		new Thread(()->{for(int i = 0; i<30; i++) tk.sale1(); },"CC").start(); }}Copy the code

Note: If you implement multithreading using Thread inheritance, the synchronous method needs to be a static method

4.3 Using Lock to Solve thread safety Problems

Since JDK 5.0, Java has provided a more powerful thread synchronization mechanism — synchronization is achieved by explicitly defining a synchronization lock object. A synchronous Lock is acted by using a Lock object. Java. Util. Concurrent. The locks. Lock interface is the tool of control multiple threads to Shared resources access. The Lock implementation provides a broader range of locking operations that can be better than obtaining methods and declarations using synchronized. They allow for a more flexible structure, can have completely different features, and can support multiple related Condition objects. Lock provides exclusive access to a shared resource. Only one thread at a time can Lock the Lock object. The thread must acquire the Lock object before accessing the shared resource.

The ReentrantLock class implements Lock, which has the same concurrency and memory semantics as synchronized. In implementing thread-safe control, ReentrantLock is commonly used to explicitly Lock and release locks.

Three, the creation of multithreading

Java uses the Thread class to represent threads, and all Thread objects must be instances of the Thread class or its subclasses. Java can create threads in four ways, as follows:

1) Create a Thread by inheriting the Thread class

2) Implement the Runnable interface to create threads

3) Create threads using Callable and Future

4) Use a thread pool such as Executors

1. Inherit the Thread class

① Define subclasses to inherit Thread.

② Subclass to override the run method of Thread class.

③ Create Thread subclass object, i.e. create Thread object.

④ Call the thread object start method to start the thread. By default, call the run method.

Note: If you just call the run method, it will be executed in the same thread that called the method, rather than starting another thread.

public class MyThread extends Thread{// Inherit the Thread class
  public void run(a){// Override the run method
   // The task that the thread needs to perform}}public class Main {public static void main(String[] args){new MyThread().start();// Create a thread instance and call the start method to start the thread}}Copy the code

2. Implement the Runnable interface to create threads

① Define subclasses to implement the Runnable interface.

Override the Runnable interface run method in a subclass.

③ Create a Thread object with the argument constructor of the Thread class, passing the Runnable subclass object as the actual argument

Constructor of the Thread class.

(4) Call the start method of the Thread class to start the Thread, which eventually calls the run method of the Runnable subclass interface.

public class MyThread implements Runnable {// Implement the Runnable interface
  public void run(a){// Override the run method}}public class Main {public static void main(String[] args){// Create a Thread object with a Thread constructorMyThread myThread=new MyThread(); 
      // Pass the Runnable interface subclass object as an actual argument to the Thread constructor. AA is the name of the ThreadThread thread=new Thread(myThread,"AA");
      // The thread startsThread (). The start (); }}Copy the code

The difference between the two methods:

  • Inheriting Thread: Thread code is stored in the Thread subclass run method.

  • Implement Runnable: Thread code exists in the run method of a subclass of the interface.

    Implementing the Runnable interface avoids the limitations of single inheritance. Multiple threads can share objects of the same interface subclass, making it ideal for multiple threads to handle the same resource.

3. Create threads using Callable and Future

Unlike the Runnable interface, the Callable interface provides a call() method as the thread body, which is more powerful than the run() method.

  • The call() method can have a return value

  • The call() method can declare an exception to be thrown

Java5 provides the Future interface to represent the return value of the Call () method in the Callable interface and an implementation class, FutureTask, that implements both the Future and Runnable interfaces. Therefore, it can be used as a target for the Thread class. Several public methods are defined in the Future interface to control the Callable tasks it is associated with.

Boolean Cancel (Boolean mayInterruptIfRunning) : View cancels the Callable task associated with the Future. V get() : V get(long timeout,TimeUnit unit) returns the return value of the call () method in Callable, which blocks and must wait until the child thread terminates: Throw TimeoutException Boolean isDone() : TimeoutException Boolean isDone() : TimeoutException Boolean isDone() Boolean isCancelled() : Returns True if the Callable task has been cancelled before the Callable task has completed normally

① Create an implementation class for the Callable interface, implement the Call () method, and then create an instance of that implementation class. (Starting with Java8, you can create Callable objects directly using Lambda expressions.)

② Use the FutureTask class to wrap the Callable object, which encapsulates the return value of the Callable object’s call() method

③ Create and start threads using FutureTask as the target of Thread objects (because FutureTask implements the Runnable interface)

④ Call the get() method of FutureTask to get the return value after the child thread finishes executing

class myThread implements Callable<Integer> {
	@Override
	public Integer call(a) throws Exception {
		System.out.println(Thread.currentThread().getName()+" Come in call");
		/ / sleep for 5 seconds
		TimeUnit.SECONDS.sleep(5);
		// Return the status code of 200
		return 200; }}public class CallableTest {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		myThread myThread = new myThread();
		FutureTask<Integer> futureTask = new FutureTask<>(myThread);
		new Thread(futureTask, "Future mission").start();
        System.out.println("End of main thread!"); Integer integer = futureTask.get(); System.out.println(integer); }}Copy the code

4. Use thread pools

Extremely heavy resources that are frequently created and destroyed, such as threads in concurrent situations, have a significant impact on performance. So create several threads in advance, put them into the thread pool, get them when they’re used, and put them back into the pool when they’re used. It can avoid frequent creation and destruction and realize reuse.

Advantage:

  • Improved response times (reduced time to create new threads)

  • Reduced resource consumption (reusing threads in the thread pool without creating them every time)

  • Easy thread management

JDK 5.0 provides ExecutorService and Executors to implement thread pooling.

ExecutorService: A true thread pool interface. The common subclass, ThreadPoolExecutor.

Void execute(Runnable command) : executes a task or command. No return value is returned. It is used to execute Runnable

Future Submit (Callable Task) : Performs a task, returns a value, and is used to perform a Callable

Void shutdown() : shuts down the connection pool

To create a thread pool:

  1. Implement the new class directly through ThreadPoolExecutor

  2. By static method of factory Executors, essentially by thread pool created by 1)

public static void main(String[] args) {
		// Create a thread pool with 10 threads

		ExecutorService executorService = Executors.newFixedThreadPool(10);
    	//ExecutorService executorService = Executors.newSingleThreadExecutor();
		for (int i = 0; i < 12; i++) {
			executorService.execute(()->{
				System.out.println(Thread.currentThread().getName());
			});
		}
		executorService.shutdown();
	}

Copy the code
  • The greatest advantage of the Executor framework introduced after 1.5 is the decoupling of task submission and execution. The person who wants to perform the Task simply describes it clearly and submits it. How the Task is executed, by whom, and when does not matter to the submitter. Specifically, submitting a Callable object to the ExecutorService (such as ThreadPoolExecutor, the most common thread pool) will result in a Future object, which will call its GET method and wait for the execution result. The Executor framework uses thread pooling internally to control the starting, executing, and closing of threads in the java.util.cocurrent package, simplifying concurrent programming. Therefore, after Java 5, starting a Thread through Executor is better than using the Thread start method. In addition to being more manageable and more efficient (Thread pooling and cost saving), there is a key point: This helps to avoid the this escape problem — if we start a thread in the constructor, because another task may start executing before the constructor ends, which may access the half-initialized object.

  • The Executor framework includes: Thread pool, Executor, Executors, ExecutorService, CompletionService, Future, Callable, and more.

  • The Executor interface defines a method execute (Runnable Command). This method receives an instance of Runable that is used to perform a task. A task is a class that implements the Runnable interface. The ExecutorService interface, which inherits from the Executor interface, provides richer ways to implement multithreading, such as ways to shut itself down, and ways to generate futures for tracking the execution of one or more asynchronous tasks. The ExecutorService can be smoothly shutdown by calling the shutdown () method, which causes the ExecutorService to stop accepting any new tasks and wait for committed tasks to complete. (committed tasks fall into two categories: ExecutorService is disabled when all committed tasks are completed. Therefore, we generally use this interface to implement and manage multithreading.

The ExecutorService lifecycle includes three states: running, shut down, and terminated. When the shutdown () method is called, the ExecutorService enters the closed state, which means that it no longer accepts new tasks, but it still executes tasks that have already been committed, and the ExecutorService is in the terminated state when all tasks that have already been committed are completed. If the shutdown () method is not called, the ExecutorService is always running, receiving and executing new tasks, and the server generally does not need to shut it down.

Execorators provides a series of factory methods for initiating thread pools and returning thread pools that implement the ExecutorService interface.

public static ExecutorService newFixedThreadPool(int nThreads)

Create a thread pool with a fixed number of threads.

public static ExecutorService newCachedThreadPool()

Create a cacheable thread pool and call execute to reuse previously constructed threads (if available). If no existing thread is available, a new thread is created and added to the pool. Terminates and removes threads from the cache that have not been used for 60 seconds.

public static ExecutorService newSingleThreadExecutor()

Create a single threaded Executor.

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

Create a thread pool that supports timed and periodic task execution and can be used in most cases as an alternative to the Timer class.

Here’s a comparison of the above four methods: All of which use the Executors’ ThreadFactory to build threads

In general, CachedTheadPool typically creates as many threads as it needs during program execution, then stops creating new threads when it reclaims old ones, so it’s a reasonable Executor’s first choice, only when this approach causes problems (such as when a large number of long-duration connection-oriented threads are required). Consider using FixedThreadPool. (Excerpt from Thinking in Java, 4th edition)

public static void main(String[] args) {
		// Create a thread pool with 10 threads
		//ExecutorService executorService = Executors.newFixedThreadPool(10);
    	ExecutorService executorService = Executors.newSingleThreadExecutor();
		for (int i = 0; i < 12; i++) {
			executorService.execute(()->{
				System.out.println(Thread.currentThread().getName());
			});
		}
		executorService.shutdown();
	}
Copy the code

After Java 5, tasks fall into two categories: classes that implement the Runnable interface and classes that implement the Callable interface. Both can be executed by the ExecutorService, but the Runnable task does not return a value, while the Callable task does. The Call () method of the Callable can only be executed through the ExecutorService Submit (Callable Task) method, and returns a Future that represents the task waiting to be completed.

The Callable interface is similar to Runnable in that both are designed for classes whose instances might be executed by another thread. But Runnable does not return a result and cannot throw a checked exception whereas Callable does return a result and may throw an exception when the return result is retrieved. The call() method in Callable is similar to Runnable’s run() method, except that it returns a value and does not.

When a Callable object is passed to the ExecutorService Submit method, the call method is automatically executed on a thread and returns the result Future object. Similarly, if you pass a Runnable object to the ExecutorService submit method, the Run method automatically executes on a thread and returns an execution result Future object, but calling the GET method on the Future object returns NULL.

Here is an example of code for an Executor to perform a Callable task:

import java.util.ArrayList;   
import java.util.List;   
import java.util.concurrent.*;   
  
public class CallableDemo{   
    public static void main(String[] args){   
        ExecutorService executorService = Executors.newCachedThreadPool();   
        List<Future<String>> resultList = new ArrayList<Future<String>>();   
        // Create five tasks and execute them
        for (int i = 0; i < 5; i++){   
            // Perform Callable type tasks with ExecutorService and store the results in a future variable
            Future<String> future = executorService.submit(new TaskWithResult(i));   
            // Store the task execution results in the List
            resultList.add(future);   
        }   
        // The result of traversing the task
        for (Future<String> fs : resultList){   
                try{   
                    If the Future return is not complete, the loop waits until the Future return is complete
                    while(! fs.isDone);// Prints the results of each thread (task) execution
                    System.out.println(fs.get());    
                }catch(InterruptedException e){   
                    e.printStackTrace();   
                }catch(ExecutionException e){   
                    e.printStackTrace();   
                }finally{   
                    // Initiates a sequential shutdown to execute previously submitted tasks, but no new tasks are acceptedexecutorService.shutdown(); }}}}class TaskWithResult implements Callable<String>{   
    private int id;   
  
    public TaskWithResult(int id){   
        this.id = id;   
    }   
    /** * The specific process of a task, which automatically executes on a thread once the task is passed to the ExecutorService submit method   
    public String call(a) throws Exception {  
        System.out.println("The call() method is automatically called!!" + Thread.currentThread().getName()); 
        // The result will be returned by the Future's get method
        return "The call() method is automatically called and the task returns:" + id + ""+ Thread.currentThread().getName(); }}Copy the code

Iv. JUC tools

1. ReentrantReadWriteLock

In reality, there is a scenario where read and write operations are performed on shared resources, and the write operations are not as frequent as the read operations. There is no problem with multiple threads reading a resource simultaneously when there is no write operation, so multiple threads should be allowed to read a shared resource simultaneously. However, if one thread wants to write to a shared resource, no other thread should be allowed to read or write to the resource.

For this scenario, JAVA provides ReentrantReadWriteLock, a read and write lock. ReentrantReadWriteLock is a shared lock related to read operations. One is a write-related lock, called an exclusive lock.

A thread enters a read lock when no other thread has a write lock. The write lock of the current thread is entered only when there is no other thread’s read lock and write lock!

class MyQueue {

	private Object obj;
	ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

	public void readObj(a) {
		/ / to read a lock
		readWriteLock.readLock().lock();
		try {
			System.out.println(Thread.currentThread().getName() + "Read:" + obj);
		} finally {
			/ / read lockreadWriteLock.readLock().unlock(); }}public void writeObj(Object obj) {
		/ / write locks
		readWriteLock.writeLock().lock();
		try {
			this.obj = obj;
			System.out.println(Thread.currentThread().getName() + "Write:" + obj);
		} finally {
			/ / write locksreadWriteLock.writeLock().unlock(); }}}/**
 * 
 */
public class ReadWriteLockDemo {
	public static void main(String[] args) throws InterruptedException {
		// Create a resource object
		MyQueue queue = new MyQueue();
		// a thread writes
		new Thread(() -> {
			queue.writeObj("Vacation.");
		}, "AA").start();
		//100 threads read
		for (int i = 0; i <= 100; i++) {
			newThread(() -> { queue.readObj(); }, String.valueOf(i)).start(); }}}Copy the code

2. CountDownLatch

CountDownLatch has two main methods that block when one or more threads call the await method. Another thread calling the countDown method decrement the counter by one (the thread calling the countDown method does not block), and when the count goes to zero, the thread blocked by the await method is woken up and continues.

/ * * * *@DescriptionThe monitor can't close the door until all six students have left
public class CountDownLatchDemo {
	public static void main(String[] args) throws InterruptedException {
		// Six students left the classroom
		/ / lock
		CountDownLatch cd = new CountDownLatch(6);
		for (int i = 1; i <= 6; i++) {
			new Thread(() -> {
				System.out.println(Thread.currentThread().getName() + "Student Number, leave the classroom!");
				// Reduce the count
				cd.countDown();
			}, String.valueOf(i)).start();
		}
		cd.await();
		// The monitor lock main thread is executed after other threads have finished executing
		System.out.println(Thread.currentThread().getName() + "Monitor locks the door!"); }}Copy the code

3. CyclicBarrier

The main thread is executed when all threads have finished

Cyclicbarriers literally mean barriers that can be used cyclically. What it does is allow a group of threads to block when they reach a barrier (also known as a synchronization point), and the barrier will not open until the last thread reaches the barrier, and all threads blocked by the barrier will continue to work. Threads enter the barrier through the await() method of the CyclicBarrier.

public class CyclicBarrierDemo {
	private static final int NUMBER = 7;

	public static void main(String[] args) {
		// CyclicBarrier(int parties, Runnable barrierAction)
		CyclicBarrier cb = new CyclicBarrier(NUMBER, () -> {
			System.out.println("Summon the Dragon!);
		});

		for (int i = 1; i <= NUMBER; i++) {
			new Thread(() -> {
				System.out.println("Call" + Thread.currentThread().getName() + "Dragon Ball");
				try {
					cb.await();
				} catch (Exception e) {
					// TODO Auto-generated catch blocke.printStackTrace(); } }, String.valueOf(i)).start(); }}}Copy the code

4.Semaphore

On the semaphore we define two operations:

When a thread invokes acquire, it either acquires a semaphore successfully (minus 1), or waits until a thread releases a semaphore, or times out.

Release actually increments the semaphore value by one and wakes up the waiting thread.

Semaphores are mainly used for two purposes, one is for mutually exclusive use of multiple shared resources and the other is for control of the number of concurrent threads.

/** * We define two operations on semaphores: Acquire when a thread calls acquire, it either acquires the semaphore successfully (the semaphore minus 1), * or it waits until another thread releases the semaphore, or it times out. Release actually increments the semaphore value by one and wakes up the waiting thread. * Semaphores are mainly used for two purposes, one is for mutually exclusive use of multiple shared resources and the other is for control of the number of concurrent threads. Situation: 3 parking Spaces, 6 cars fighting for parking Spaces * */
public class SemaphoreDemo {
	public static void main(String[] args) {
		// Create three parking Spaces
		Semaphore sp = new Semaphore(3);
		// Six cars grab three parking Spaces
		for (int i = 1; i <= 6; i++) {
			new Thread(() -> {
				try {
					// Enter the parking space
					sp.acquire();
					System.out.println(Thread.currentThread().getName() + "Car Number one pulls into the parking space.");
					/ / stop in three seconds
					TimeUnit.SECONDS.sleep(3);
					// Pull out of the parking space
					sp.release();
					System.out.println(Thread.currentThread().getName() + "Car Number one exits the parking space.");
				} catch (Exception e) {
					// TODO Auto-generated catch blocke.printStackTrace(); } }, String.valueOf(i)).start(); }}}Copy the code

Conclusion:

That’s all for this article. I am also the first contact, if there is a mistake, hope the big guy give directions!