At present, we have updated the “Java Concurrent Programming” and “Docker tutorial”, welcome to pay attention to the “back-end advanced road”, easy to read all articles.

Java concurrent programming:

  • Java Concurrent Programming Series -(1) Concurrent programming basics
  • Java Concurrent Programming Series -(2) Concurrency utility classes for threads
  • Java Concurrent Programming series -(3) Atomic Operations with CAS
  • Java Concurrent Programming series -(4) Explicit locks with AQS
  • Java Concurrent Programming Series -(5) Java concurrent containers
  • Java Concurrent Programming Series -(6) Java thread pools
  • Java Concurrent Programming Series -(7) Java thread safety
  • Java Concurrent Programming series -(8) JMM and underlying implementation principles
  • Java Concurrent Programming Series -(9) Concurrency in JDK 8/9/10

Docker tutorial:

  • Docker series -(1) Principle and basic operation
  • Docker series -(2) image production and release
  • Docker series -(3) Docker-compose usage and load balancing

JVM Performance optimization:

  • JVM Performance Optimization Series -(1) Java Memory region
  • JVM Performance Optimization Series -(2) Garbage collector and Memory allocation Strategies

1. Fundamentals of concurrent programming

1.1 Basic Concepts

CPU core and number of threads

Java through the means of multithreading to achieve concurrency, for the single-processor machine, macroscopically parallel execution of multithreading is through the SCHEDULING of CPU to achieve, microcosmic CPU will only run a thread at a certain moment. In fact, if these tasks there is no obstruction, who is a task of program because the program is beyond the control of certain conditions (usually the I/O) and cause can’t continue, because in the task switching between will produce cost, so the high efficiency of parallel efficiency may not be sequential, there would be no point in parallel.

Generally speaking, the relationship between the number of CPU cores and the number of threads is the number of cores: the number of threads =1:1; But with hyperthreading, it can be 1:2 or more.

CPU Scheduling mode

The CPU uses the time slice rotation mechanism to schedule different threads. This mechanism is also called RR scheduling. Context switching may occur. If the number of threads is too large, there may be a large thread switching overhead.

Threads and processes

Process: a process is a running activity of a program with certain independent functions on a data set. It is an independent unit of the system for resource allocation and scheduling. (including program segment, related data segment, and process control block PCB)

Thread: A thread is an entity of a process. It is the basic unit of CPU scheduling and dispatch. It is a smaller unit that can run independently. A thread has virtually no system resources of its own, only a few resources that are essential to running (such as a program counter, a set of registers, and a stack), but it can share all the resources owned by a process with other threads that belong to the same process.

Relationships: One thread can create and destroy another thread; Multiple threads in the same process can execute concurrently. Compared to a process, a thread is a concept that is closer to an execution body. It can share data with other threads in the same process, but has its own stack space, and has a separate execution sequence. Differences: The main difference is that they are different operating system resource management styles. Processes have independent address Spaces, a crash of one process in protected mode does not affect other processes, and threads are just different execution paths within a process. Thread has its own stack and local variables, but there is no separate address space between threads, a thread dead is equal to the whole process dead, so multi-process procedures than multithreaded procedures robust, but in the process switch, the cost of resources is larger, the efficiency is poor. However, for concurrent operations that require simultaneous and variable sharing, only threads, not processes, can be used.

Advantages and disadvantages: Threads and processes have their own advantages and disadvantages. Thread execution costs little, but it is not conducive to resource management and protection. The process is the opposite. At the same time, threads are suitable for running on SMP machines, while processes can be migrated across machines.

Parallelism and concurrency

Parallelism: The ability to do things simultaneously at the same time.

Concurrency: The ability to deal with things in a unit of time.

Implications and considerations for high concurrency programming

Significance and benefits: make full use of CPU resources, speed up the user response time, program modularization, asynchronous problems.

Defects and Precautions:

  • Threads share resources and conflict exists.
  • Easy to cause deadlock;
  • Enabling too many threads can cause huge CPU and memory overhead and potentially crash the machine.

1.2 Starting and stopping a Thread

Three ways to start a thread

There are 3 ways to start a thread in Java, or to put it another way there are 3 ways to implement multithreading:

  • Thread class inheritance
Private static class UseExtendsThread extends Thread {@override public void Overriderun() {
			System.out.println("I am from the extends thread."); }}Copy the code
  • Implement the Runnable interface
/ / private static class UseRun implements Runnable{@override public voidrun() {
			System.out.println("I am implements Runnable"); }}Copy the code
/* Implement Callable interface, */ private static class UseCall implements Callable<String>{@override public String Call () throws Exception {@override public String Call () throws Exception { System.out.println("I am implements Callable");
			return "I am the CallResult"; }}Copy the code
  • Implement Callable interface

The main difference between Runnable and Callable is that the latter can return a value.

Here’s how to start in main,

	public static void main(String[] args) 
			throws InterruptedException, ExecutionException {
		UseExtendsThread useExtendsThread = new UseExtendsThread();
		useExtendsThread.start();
		
		UseRun useRun = new UseRun();
		new Thread(useRun).start();
		Thread t = new Thread(useRun);
		t.interrupt();
		
		UseCall useCall = new UseCall();
		FutureTask<String> futureTask = new FutureTask<>(useCall);
		new Thread(futureTask).start();
		System.out.println(futureTask.get());
	}
Copy the code

Note that the Callable interface is typically used in conjunction with FutureTask.

Stop of thread

Methods stop(), suspend(), and resume() are provided in Java to stop, suspend, and resume suspended threads, but these three methods are no longer recommended.

  • The suspend () method causes the thread to suspend without releasing any resources, and if other threads try to access the lock it holds, it will not run.

  • The resume() method is used to resume a program suspended from suspend, but if Resume runs before suspend, the suspended thread will continue to suspend and the locks it holds will not be released, potentially causing the entire system to fail.

  • For the stop() method, it simply stops the thread, possibly causing it to fail to release resources properly.

Stop threads safely – Interrupt (), isInterrupted(), interrupted()

Java threads are collaborative, not preemptive.

  • Calling interrupt() on a thread interrupts a thread, but it does not force the thread to shut down. It simply signals the thread to true. It is up to the thread to interrupt or not interrupt.
  • IsInterrupted () checks whether the current thread isInterrupted.
  • The static method interrupted() checks whether the current thread is interrupted and changes the interrupt flag bit to false.

The thread’s interrupt flag is reset to false if InterruptedException is thrown. If we do interrupt the thread, we need to call interrupt() again in the catch block ourselves.

In the following example, when the main thread tries to interrupt the child thread, the sleep function throws an exception that clears the interrupt flag. To interrupt the thread, we need to call interrupt() again.

public class HasInterrputException {
	
	private static class UseThread extends Thread{
		
		public UseThread(String name) {
			super(name);
		}
		
		@Override
		public void run() {
			String threadName = Thread.currentThread().getName();
			while(! isInterrupted()) { try { System.out.println(threadName +" is running");
					Thread.sleep(300);
				} catch (InterruptedException e) {
					System.out.println(threadName+" catch interrput flag is "
							+isInterrupted());
					interrupt();
					e.printStackTrace();
				}	
			}
			System.out.println(threadName+" interrput flag is "
					+isInterrupted());
		}
	}

	public static void main(String[] args) throws InterruptedException {
		Thread endThread = new UseThread("exampleThread");
		endThread.start();
		System.out.println("Main Thread sleep 800 ms");
		Thread.sleep(800);
		System.out.println("Main begin interrupt thread !"); endThread.interrupt(); }}Copy the code

1.3 Thread Status

A thread can have six states:

  • New (New created)
  • Runnable
  • Blocked.
  • Waiting for
  • Timed waiting
  • Terminated

To determine the state of the current thread, call the getState() method.

Newly created thread

When a new thread is created with the new operator, such as newThread (r), the thread has not yet started running. This means that it’s in a New state, so when a thread is in the newly created state and the program hasn’t started running the code in the thread has some basic work to do before the thread can run

Operational condition

Once the start method is called, the thread is in the runnable state. A runnable pole may or may not be running, depending on how long the operating system provides threads to run (the Java specification does not treat this as a separate state).

Block, wait, timing wait states

  • When a thread attempts to acquire an internal object lock (other than a lock in the java.util.concurrent library that is held by another thread), the thread enters the blocked state. When all other threads release the lock and the thread scheduler allows this thread to hold it, the thread becomes non-blocking.

  • A thread enters the wait state when it waits for another thread to notify the scheduler of a condition. This happens when you call the Object.wait or Thread.join methods or wait for a Lock or Condition in the java.util.Concurrent library. Note that the blocked state is very different from the wait state.

  • Several methods have a timeout parameter. Calling them causes the thread to enter a timed waiting state, which remains in place until the timeout expires or the appropriate notification is received, Sleep and timeouts of Object.wait, Thread.join, Lock, try Lock, and Condition. Await are methods with timeout parameters.

Termination status

A thread is terminated for one of two reasons:

  • Natural death due to normal exit of the run method.
  • An unexpected death due to an uncaught exception terminating the run method.

A thread can be killed by calling its stop method, which throws the ThreadDeath error object, thereby killing the thread.

The switch between thread states is shown below:

1.4 Thread Properties

Thread priority

In Java, each thread has a priority and by default inherits the priority of the parent thread. The setPriority method can be used to set the priority of a thread between MIN_PRIORITY (1) and MAX_PRIORITY (10).

Note: The implementation of priorities is highly system dependent, Java priorities are mapped to the host platform priorities, so it is possible to have higher or lower priorities, and in extreme cases, all priorities are mapped to the same host priority, so do not over-rely on priorities.

Improperly set priorities can cause low-priority threads to never run.

Daemon thread

Threads can be converted to daemons by calling t.setdaemon (true). The only function of a daemon thread is to serve other threads. When only daemon threads remain, the virtual machine exits.

Daemon threads should never access inherent resources such as files, databases, etc., because it may break in the middle of an operation.

1.5 Thread Synchronization and Sharing

In most practical multithreaded applications, two or more threads need to share access to the same data. If multiple threads are not coordinated and synchronized, the correctness of accessing shared resources cannot be guaranteed. Java provides several tools for thread sharing.

Synchronized built-in lock

Synchronized synchronized synchronized synchronized synchronized synchronized synchronized

  • An object lock locks an object instance of a class.
  • A Class lock locks the Class object of each Class. Each Class has only one Class object in a VM, so there is only one Class lock.

Specifically, there are several uses,

1. Decorate the code block

The code block to be decorated is called a synchronous block, and its scope is the code enclosed in curly braces {}, and its scope is the object that calls the code block.

When one thread accesses a block of synchronized(this) code in an object, other threads attempting to access the object are blocked. Similar to the following,

/** * implements Runnable {private static int count; publicSyncThread() {
      count = 0;
   }
   public  void run() {
      synchronized(this) {
         for (int i = 0; i < 5; i++) {
            try {
               System.out.println(Thread.currentThread().getName() + ":" + (count++));
               Thread.sleep(100);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }
      }
   }
   public int getCount() {
      returncount; }}Copy the code

Note: When one thread accesses a synchronized(this) synchronized block of an object, another thread can still access a non-synchronized (this) synchronized block of that object.

2. Decorate a method

Synchronized describes a method simply by preceded by Synchronized. A modifier is similar to a modifier block, except that a modifier block is a range enclosed in braces, whereas a modifier range is the entire function.

public synchronized void run() {
   for (int i = 0; i < 5; i ++) {
      try {
         System.out.println(Thread.currentThread().getName() + ":"+ (count++)); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }}}Copy the code

Although synchronized can be used to define methods, synchronized is not part of the method definition, and therefore the synchronized keyword cannot be inherited. If a method in a parent class uses the synchronized keyword and a method in a subclass overrides it, the method in the subclass is not synchronized by default, and you must explicitly add the synchronized keyword to the method in the subclass.

3. Decorate a static method

Synchronized can also modify a static method as follows:

public synchronized static void method() {
   // todo
}
Copy the code

4. Decorate a class

Synchronized also acts on a class and can be used as follows:

class ClassName {
   public void method() {
      synchronized(ClassName.class) {
         // todo
      }
   }
}
Copy the code

Synchronized applies to a class T by locking it; all objects of T use the same lock.

conclusion

A. Synchronized acquires A non-static lock if the synchronized keyword is applied to A method or an object; If synchronized acts on a static method or a class, it acquires the same lock for all objects of that class. B. Each object has only a lock associated with it. Whoever holds this lock can run the code it controls. C. Implementing synchronization is expensive and can even cause deadlocks, so avoid unnecessary synchronization control.

Volatile variables

The Java language provides a weaker synchronization mechanism, known as volatile variables, to ensure that changes to variables are notified to other threads.

When a variable is declared as volatile, both the compiler and the runtime notice that the variable is shared, so changes are not reordered with other memory operations. Volatile variables are not cached in registers or other places invisible to the processor, so when volatile variables are read, they always return the most recently written value.

ThreadLocal

A more prescriptive approach to maintaining thread closure is ThreadLocal, a class that associates a value in a thread with the object holding the value. ThreadLocal provides access interfaces and methods such as GET and set that keep a separate copy for each thread that uses the variable, so get always returns the latest value set by the current executing thread when it calls set.

When a thread first calls the threadlocal. get method, initialValue is called to get the initialValue. Conceptually, you can think of a ThreadLocal as containing a Map<Thread, T> object that holds thread-specific values, but the implementation of ThreadLocal does not. These specific values are stored in the Thread object and are garbage collected when the Thread terminates.

You can refer to the following examples for details.

Public class UseThreadLocal {// Type Map<Thread,Integer> static ThreadLocal<Integer> threadLaocl = new ThreadLocal<Integer>(){@override protected IntegerinitialValue() {
			return1; }}; /** * run 3 threads */ public voidStartThreadArray(){
        Thread[] runs = new Thread[3];
        for(int i=0; i<runs.length; i++){ runs[i]=new Thread(new TestThread(i)); }for(int i=0; i<runs.length; i++){ runs[i].start(); }} /** * */ public static class implements Runnable{int id; public static class implements Runnable; public TestThread(int id){ this.id = id; } public voidrun() {
            System.out.println(Thread.currentThread().getName()+":start"); Integer s = threadLaocl.get(); // Get the variable s = s+id; threadLaocl.set(s); System.out.println(Thread.currentThread().getName()+":"
            +threadLaocl.get());
            //threadLaocl.remove();
        }
    }

    public static void main(String[] args){
    	UseThreadLocal test= new UseThreadLocal(); test.StartThreadArray(); }}Copy the code

1.6 Collaboration between threads

Wait and notify Wait, notify, and notifyAll

The JDK provides wait, notify, and notifyAll methods to collaborate with multiple threads. Note that these methods are in the Object class.

  • After calling object.wait (), the current thread waits on the Object; Wait until another thread calls the corresponding notifyAll object.
  • If more than one thread on the object calls wait(), the notifyAll() method is called to wake up all the threads.

Note: Generally, wait and notifyAll work together because when multiple threads call wait, they enter the wait queue of the object. If notify is called, only one thread is awakened randomly from the wait list. It may not be what we want.

Here’s how wait and notify and notifyAll work:

Here are some examples of notify and wait.

public class Express {
    public final static String CITY = "ShangHai"; private int km; /* private String site; /* Express destination */ publicExpress() { } public Express(int km, String site) { this.km = km; this.site = site; } /* Change the number of kilometers, then notify atwaitSynchronized void */ public synchronized voidchangeKm(){ this.km = 101; notifyAll(); // notify(); } /* Change the location, then notify inwait*/ public synchronized voidchangeSite(){
    	this.site = "BeiJing";
    	notify();
    }

    public synchronized void waitKm() {while(this.km<=100) {
    		try {
				System.out.println("check km thread["+Thread.currentThread().getId()
						+"] is still waiting.");
				wait(a); System.out.println("check km thread["+Thread.currentThread().getId()
						+"] is notified.");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
    	}
    }

    public synchronized void waitSite() {while(CITY.equals(this.site)) {
    		try {
				System.out.println("check site thread["+Thread.currentThread().getId()
						+"] is still waiting.");
				wait(a); System.out.println("check site thread["+Thread.currentThread().getId()
						+"] is notified."); } catch (InterruptedException e) { e.printStackTrace(); }}}}Copy the code

In the test program, six threads are started and all wait threads are awakened after a changeKm call. But because the city has not changed, the thread checking the city continues to wait after being woken up.

public class TestWN { private static Express express = new Express(0,Express.CITY); */ private static class CheckKm extends Thread{@override public void */ Private static class CheckKm extends Thread{@override public voidrun() { express.waitKm(); }} private static class CheckSite extends Thread{@override public void */ Private static class CheckSite extends Thread{@override public voidrun() {
        	express.waitSite();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for(int i=0; i<3; I++){// three threads new CheckSite().start(); }for(int i=0; i<3; I++){// change the mileage new CheckKm().start(); } Thread.sleep(1000); express.changeKm(); // Express location change}}Copy the code

Notice that the methods that call wait and notify both have the synchronized keyword, because before calling these methods, you need to obtain the target object’s monitor, which is released after execution. When a thread is woken up, the first thing it does is try to get the monitor for the target object, and if it does, it executes subsequent code, otherwise it waits to get the monitor.

Wait for thread completion (join) and Courtesy (Yeild)

The join method

When a thread’s input may depend heavily on the output of another thread or multiple threads, the thread must wait for the dependent thread to complete before continuing. The JDK provides the join operation to do this,

For example, if thread A executes thread B.join(), thread A must wait for thread B to complete before thread A can continue its work.

Yeild method

Thread.yield() is a static method that, once executed, causes the current Thread to yield CPU, but yielding CPU does not mean the current Thread will not execute. The current thread will compete for CPU resources after relinquishing the CPU, but it may or may not be allocated.

Yield (), sleep(), wait(), notify(), etc

  • Yield: Yields a time slice without releasing the lock

  • Sleep: The thread goes to sleep and does not release the lock

  • Wait: execute the method only after the lock is obtained. After the lock is released, the method enters the wait queue for the lock. After the method is returned by notify, the method obtains the lock again.

  • Notify: the lock must be obtained before execution. The lock will not be released immediately after execution. Instead, a thread in the waiting queue will release the lock after execution of the synchronized code block is complete. The lock itself will not be released.


This article was originally published on teckee.github. IO/by Teckee.github

Search the public account “The Road to Back-end Improvement”, and immediately get the latest articles and BATJ high-quality interview courses worth 2,000 yuan.