preface

This article introduces the basics of multithreading and concurrency in Java. It is suitable for beginners. If you want to read a little more about multithreading and concurrency, check out my other blog, “Lock”.

The difference between thread and process

In the early stage of computer development, each computer is serial to perform tasks, if the IO needs to meet the place, but also need to wait for a long time of user IO, after a period of time with the batch processing computer, it can batch serial processing user instructions, but the nature is serial, or not concurrent execution. How do you solve the problem of concurrent execution? Therefore, the concept of process was introduced, each process occupies a share of memory space, the process is the smallest unit of memory allocation, mutual operation does not interfere with each other and can switch between, now we see multiple processes running “simultaneously”, in fact, is the effect of high-speed process switching.

So with threads, our computer system seems to be perfect, why go into threads? If a process has multiple subtasks, a process usually needs to execute these subtasks one by one. However, these subtasks are usually independent and can be executed concurrently, requiring finer granularity of CPU switching. Therefore, the concept of threads was introduced. Threads belong to a process, share the memory resources of the process, and switch between them more quickly.

The difference between a process and a thread

1. A process is the smallest unit of resource allocation, and a thread is the smallest unit of CPU scheduling. All process-related resources are recorded in the PCB.

2. A thread belongs to a process and shares resources of the process. Threads consist only of stack registers, program counters, and TCBS.

3. Processes can be regarded as independent applications, but threads cannot be regarded as independent applications.

4. Processes have independent address Spaces that do not affect each other, and threads are just different execution paths of processes. If a thread dies, the process dies. So multi-process programs are more robust than multi-threaded programs, but switching consumes more resources.

The relationship between processes and threads in Java:

1. Running a program creates a process that contains at least one thread.

2. Each process corresponds to one JVM instance, with multiple threads sharing the heap within the JVM.

3.Java uses a single-threaded programming model, in which the main thread is automatically created.

4. The main thread can create child threads. In principle, the execution must be completed after the child threads.

The difference between a thread’s start method and a run method

  • The difference between

    There are two ways to create threads in Java, either by inheriting Thread or by implementing the Runnable interface, which requires overriding the Run method. Calling start creates a new thread and starts it. The run method is just a callback to start the thread. If run is called, the thread executing run will not be the newly created thread, but if start is used, the thread executing run will be the one we started.

  • Program verification

    public class Main {
    	public static void main(String[] args) {
    		Thread thread = new Thread(newSubThread()); thread.run(); thread.start(); }}class SubThread implements Runnable{
    
    	@Override
    	public void run(a) {
    		// TODO Auto-generated method stub
    		System.out.println("Thread executing this method :"+Thread.currentThread().getName()); }}Copy the code

Relationship between Thread and Runnable

  • Thread the source code

  • A Runnable source

  • The difference between

    The Runnable interface has only one unimplemented run method. Runnable does not independently start a Thread. Instead, Runnable relies on Thread to create a Thread and execute its own run method. To execute the corresponding business logic, this class has multithreaded characteristics.

  • Create child threads by inheriting Thread and implementing Runable interface respectively

    • Create child threads by inheriting the Thread class

      public class Main extends Thread{
      	public static void main(String[] args) {
      		Main main = new Main();
      		main.start();
      	}
      	@Override
      	public void run(a) {
      		System.out.println("Succeeded in creating child Thread by inheriting Thread interface, current Thread name:"+Thread.currentThread().getName()); }}Copy the code

      Running results:

    • Create child threads by implementing the Runnable interface

      public class Main{
      	public static void main(String[] args) {
      		SubThread subThread = new SubThread();
      		Thread thread = newThread(subThread); thread.start(); }}class SubThread implements Runnable{
      
      	@Override
      	public void run(a) {
      		// TODO Auto-generated method stub
      		System.out.println("Successfully created child thread by implementing Runnable interface, current thread name :"+Thread.currentThread().getName()); }}Copy the code

      Running results:

    • Create child threads using anonymous inner classes

      public class Main{
      	public static void main(String[] args) {
      		Thread thread = new Thread(new Runnable() {
      			@Override
      			public void run(a) {
      				// TODO Auto-generated method stub
      				System.out.println("Thread created using anonymous inner class succeeded, current thread name :"+Thread.currentThread().getName()); }}); thread.start(); }}Copy the code

      Running results:

  • Relationship between

    1.Thread is a class that implements the Runnable interface, enabling Run to support multithreading. 2

    2. The Runnable interface is recommended to make programs more flexible due to the single inheritance principle of the class.

How to handle multithreaded return values

Through the learning just now, we know that multithreaded logic needs to be put into the run method to execute, and the run method is no return value, so encounter the need to return the value of the situation is not easy to solve, so how to achieve the child thread return value?

  • Main thread wait method

    By making the main thread wait until the child thread finishes running.

    Implementation method:

    public class Main{
    	static String str;
    	public static void main(String[] args) {
    		Thread thread = new Thread(new Runnable() {
    			@Override
    			public void run(a) {
    				str="Child thread completes execution"; }}); thread.start();// If STR has not been assigned by the child thread, the rotation will continue
    		while(str==null) {} System.out.println(str); }}Copy the code
  • Use the join() method in Thread

    The join() method blocks the current thread while the child threads finish processing.

    Implementation method:

    public class Main{
    	static String str;
    	public static void main(String[] args) {
    		Thread thread = new Thread(new Runnable() {
    			@Override
    			public void run(a) {
    				str="Child thread completes execution"; }}); thread.start();// If STR has not been assigned by the child thread, the rotation will continue
    		try {
    			thread.join();
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch blocke.printStackTrace(); } System.out.println(str); }}Copy the code

    The join method can achieve more precise control than the main thread waiting method, but the control granularity of the Join method is not fine enough. For example, if I need to control the child thread to assign a particular value to the string before executing the main thread, the join method cannot do this.

  • Through the Callable interface: retrieved from FutureTask or thread pool

    Before JDK1.5, threads did not return values, and programs often had trouble retrieving the return values of child threads. Now Java has its own return value thread, that is, the thread implementing the Callable interface. After executing the thread implementing the Callable interface, it can obtain a Future object. Calling a GET method on the Object executes the logic of the child thread and retrieves the returned Object.

    Implementation method 1 (error in obtaining this method directly) :

    public class Main implements Callable<String>{
    
    	@Override
    	public String call(a) throws Exception {
    		// TODO Auto-generated method stub
    		String str = "I'm a child thread with a return value.";
    		return str;
    	}
    	public static void main(String[] args) {
    		Main main = new Main();
    		try {
    			String str = main.call();
    			/* Why is this the wrong way? As mentioned above, the run() method differs from the start() method in that the run() method is a callback after the thread is started. If called directly, the thread is not created and is executed by the main thread. If you call call directly, no child thread is created. Instead, you call the instance method of the class and get the return value. There is no child thread at all. * /
    			System.out.println(str);
    		} catch (Exception e) {
    			// TODO Auto-generated catch blocke.printStackTrace(); }}}Copy the code

    Running results:

    Implementation 2 (using FutureTask) :

    public class Main implements Callable<String>{
    
    	@Override
    	public String call(a) throws Exception {
    		// TODO Auto-generated method stub
    		String str = "I'm a child thread with a return value.";
    		return str;
    	}
    	public static void main(String[] args) {
    		FutureTask<String> task = new FutureTask<String>(new Main());
    		new Thread(task).start();
    		try {
    			if(! task.isDone()) { System.out.println("The mission was not executed.");
    			}
    			System.out.println("Waiting...");
    			Thread.sleep(3000);
    			System.out.println(task.get());
    			
    		} catch (InterruptedException | ExecutionException e) {
    			// TODO Auto-generated catch blocke.printStackTrace(); }}}Copy the code

    Running results:

    Implementation method 3 (using thread pool with Future fetch) :

    public class Main implements Callable<String>{
    
    	@Override
    	public String call(a) throws Exception {
    		// TODO Auto-generated method stub
    		String str = "I'm a child thread with a return value.";
    		return str;
    	}
    	public static void main(String[] args) throws InterruptedException, ExecutionException {
    		ExecutorService newCacheThreadPool = Executors.newCachedThreadPool(); 
    		Future<String> future = newCacheThreadPool.submit(new Main());
    		if(! future.isDone()) { System.out.println("Thread not finished executing");
    		}
    		System.out.println("Waiting");
    		Thread.sleep(300); System.out.println(future.get()); newCacheThreadPool.shutdown(); }}Copy the code

    Running results:

Thread state

Java threads are divided into six states: new, Runnable, Waiting indefinitely, TimeWaiting, Blocked, and Terminated.

  • New

    The new state is when a thread is in the created but not started state. The thread in this state is only created, but has not started executing its internal logic.

  • Run (Runnable)

    The Running state includes Ready and Running. When a thread calls the start method, it does not execute immediately, but compets for CPU. When the thread does not execute, it is in the Ready state, and when it obtains the CPU time slice, it changes from the Ready state to Running state.

  • Waiting.

    A thread in the wait state will not wake up automatically, but wait to be woken up by another thread. In the wait state, the thread will not be allocated time by the CPU and will remain blocked. The following operations cause the thread to wait:

    1. There is no object.wait () method with timeout parameter.

    2. Thread.join() method without timeout parameter.

    3. Locksupport.park () method (The park method is not actually provided by LockSupport, but in Unsafe, LockSupport just wraps it. Check out my other blog, Locks, This method is mentioned in ReentrantLock source code parsing.

  • TimeWaiting

    The CPU also does not allocate time slices for a thread that is waiting in the deadline. However, the thread that is waiting in the deadline does not need to be explicitly woken up by other threads. Instead, the system wakes up automatically when the waiting time ends. The following operations cause a thread to wait:

    1. Thread. Sleep () method.

    2. Set the object.wait () method with the timeout parameter.

    3. Set the thread.join () method with the timeout parameter.

    4. LockSupport. ParkNanos () method.

    5. LockSupport. ParkUntil () method.

  • They are Blocked.

    When multiple threads enter the same shared area, such as a Synchronized block or an area controlled by ReentrantLock, the whole lock will be captured. The thread that successfully obtains the lock will continue to execute, while the thread that does not obtain the lock will enter the blocking state and wait for the lock to be acquired.

  • The end (Terminated)

    The thread state of a terminated thread that has finished executing.

The difference between “Sleep” and “Wait”

Sleep and Wait can both cause a thread to enter a finite Wait state. What is the difference between the two methods?

1. The sleep method is provided by Thread and the wait method by Object.

2. The sleep method can be used anywhere, while the wait method can only be used in synchronized blocks or methods.

3. The sleep method does not release the lock, while the wait method does not only release the CPU but also release the lock.

Test code:

public class Main{
	public static void main(String[] args) {
		Thread threadA = new Thread(new ThreadA());
		Thread threadB = new Thread(new ThreadB());
		
		threadA.setName("threadA");
		threadB.setName("threadB");
		
		threadA.start();
		threadB.start();
	}

	public static synchronized void print(a) {
		System.out.println("Current thread :"+Thread.currentThread().getName()+"Executive Sleep");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("Current thread :"+Thread.currentThread().getName()+"Executive Wait");
		try {
			Main.class.wait(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("Current thread :"+Thread.currentThread().getName()+"Executed"); }}class ThreadA implements Runnable{
	@Override
	public void run(a) {
		// TODO Auto-generated method stubMain.print(); }}class ThreadB implements Runnable{
	@Override
	public void run(a) {
		// TODO Auto-generated method stubMain.print(); }}Copy the code

Execution Result:

It can be analyzed from the above results: When after executing thread A sleep, wait A second awakened after continue hold locks, after the execution of code, and performing the wait, immediately release the lock, and release the CPU is not only release the lock, then thread B hold locks immediately began to perform, and thread A to perform the same steps, execute when the thread B wait method, release the lock, And then thread A takes the lock and prints out the first one, and then thread B prints out the first one.

The difference between notify and notifyAll

  • notify

    To wake up a thread in wait state, notify:

    public class Main{
    	public static void main(String[] args) {
    		Object lock = new Object();
    		Thread threadA = new Thread(new Runnable() {
    			
    			@Override
    			public void run(a) {
    				synchronized (lock) {
    					try {
    						lock.wait();
    					} catch (InterruptedException e) {
    						// TODO Auto-generated catch blocke.printStackTrace(); } print(); }}}); Thread threadB =new Thread(new Runnable() {
    			
    			@Override
    			public void run(a) {
    				synchronized(lock) { print(); lock.notify(); }}}); threadA.setName("threadA");
    		threadB.setName("threadB");
    		
    		threadA.start();
    		threadB.start();
    	}
    
    	public static void print(a) {
    			System.out.println("Current thread :"+Thread.currentThread().getName()+"Executive print");
    			try {
    				Thread.sleep(1000);
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    			System.out.println("Current thread :"+Thread.currentThread().getName()+"Executed"); }}Copy the code

    Execution Result:

    Code explanation: Thread A immediately starts calling wait into the infinite wait state, if there is no other threads to awaken it, it will have been waiting for, so at this point B hold locks began to perform, and calls the notify method when completed, this method can awaken A thread wait state, then A thread to start executing the rest of the code.

  • notifyAll

    NotifyAll is used to wake up all waiting threads so that all waiting threads become ready to re-compete for the lock.

    public class Main{
    	public static void main(String[] args) {
    		Object lock = new Object();
    		Thread threadA = new Thread(new Runnable() {
    			
    			@Override
    			public void run(a) {
    				synchronized (lock) {
    					try {
    						lock.wait();
    					} catch (InterruptedException e) {
    						// TODO Auto-generated catch blocke.printStackTrace(); } print(); }}}); Thread threadB =new Thread(new Runnable() {
    			
    			@Override
    			public void run(a) {
    				synchronized(lock) { print(); lock.notifyAll(); }}}); threadA.setName("threadA");
    		threadB.setName("threadB");
    		
    		threadA.start();
    		threadB.start();
    	}
    
    	public static void print(a) {
    			System.out.println("Current thread :"+Thread.currentThread().getName()+"Executive print");
    			try {
    				Thread.sleep(1000);
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    			System.out.println("Current thread :"+Thread.currentThread().getName()+"Executed"); }}Copy the code

    Execution Result:

    To wake up thread A in the previous example, not only notify can do this, but notifyAll can also do this. What’s the difference?

  • The difference between

    Java synchronized lock is a synchronized lock that can be synchronized with a Java object. A Monitor object contains a lock pool and a wait pool. (This part is covered in detail in another article, Locks, but I’ll cover it briefly here.)

    Lock pool: If multiple objects enter the synchronized block to compete for the lock, and one object has acquired the lock, the remaining objects will directly enter the lock pool.

    Wait pool: If a thread calls the wait method of an object, the thread will enter the wait pool directly, and the object in the wait pool will not fight for the lock, but will wait to be woken up.

    There is a difference between notify and notifyAll.

    NotifyAll sends all the threads in the wait pool into the lock pool to compete for the lock, while Notify sends only one thread randomly to compete for the lock.

Yield method

  • concept

        /** * A hint to the scheduler that the current thread is willing to yield * its current use of a processor. The scheduler is free to ignore this * hint. * * <p> Yield is a heuristic attempt to improve relative progression * between threads that would otherwise over-utilise a CPU. Its use * should be combined with detailed profiling and benchmarking to * ensure that it actually has the desired effect. * * <p> It is rarely appropriate to use this method. It may be useful * for debugging or testing purposes, where it may help to reproduce * bugs due to race conditions. It may also be useful when designing * concurrency control  constructs such as the ones in the * {@link java.util.concurrent.locks} package.
         */
        public static native void yield(a);
    Copy the code

    The yield source code has a long comment that goes something like this: The current thread calls the yield method, gives the current thread scheduler a suggested that the use of the current thread is willing to yield the CPU, but its role should be combined with the detailed analysis and testing to ensure that had the desired effect, because the scheduler may ignore the hint, using this method is not so suitable, may use it in a test environment would be better.

    Testing:

    public class Main{
    	public static void main(String[] args) {
    		Thread threadA = new Thread(new Runnable() {
    			
    			@Override
    			public void run(a) {
    				System.out.println("ThreadA performing yield");
    				Thread.yield();
    				System.out.println("ThreadA performs yield completion"); }}); Thread threadB =new Thread(new Runnable() {
    			
    			@Override
    			public void run(a) {
    				System.out.println("ThreadB performing yield");
    				Thread.yield();
    				System.out.println("ThreadB performs yield completion"); }}); threadA.setName("threadA");
    		threadB.setName("threadB");
    		
    		threadA.start();
    		threadB.start();
    	}
    Copy the code

    Test results:

    As you can see, there are different test results, so let’s pick two of them.

    The first result is that thread A completes the yield method and cedes the CPU to thread B. The two threads then continue to execute the rest of the code.

    In the second case, thread A yields the CPU to thread B, but thread B does not yield the CPU and continues to execute, and the system ignores the hint.

Interrupt method

  • Suspend the thread

    Interrupts can interrupt a thread. Before interrupts, it was common to use the stop method to terminate a thread. However, the stop method is too violent. This can cause subsequent cleanup of the interrupted thread to fail, causing unnecessary exceptions and pitfalls, and potentially causing data synchronization problems.

  • Gentle interrupt methods

    When an interrupt method is called to terminate a thread, it does not violently force the thread to terminate. Instead, it notifies the thread that it should be interrupted. Like yield, this is a hint as to whether it should be interrupted. It’s up to the interrupted thread to decide. When the interrupt method is called on a thread:

    1. If the thread is blocked, it exits the blocking state immediately and throws InterruptedException.

    2. If the thread is in the running state, the interrupt flag bit of the thread is set to true, and the thread continues to run without being affected. When the running ends, the thread decides whether to interrupt.

The thread pool

The introduction of the thread pool is used to solve the daily development of multithreading development, if the developer needs to use to a huge number of threads, so these threads are created and destroyed frequently, may cause certain influence on the system, the possible system in these threads created and destroyed the amount of time will be longer than the actual demand. In addition, thread management becomes a big problem in the case of many threads, and developers often shift their focus from functionality to managing threads that are cluttered, which can be very exhausting.

  • Use Executors to create different thread pools to meet the requirements of different scenarios

    • newFixThreadPool(int nThreads)

      A thread pool that specifies the number of worker threads.

    • newCachedThreadPool()

      A thread pool that handles a large number of interrupt event work tasks,

      1. Try to cache the thread and reuse it. When no cached thread is available, a new worker thread is created.

      2. If the idle time of a thread exceeds the threshold, the thread is terminated and removed from the cache.

      3. When the system is idle for a long time, it consumes no resources.

    • newSingleThreadExecutor()

      A unique worker thread is created to execute the task, and if the thread terminates abnormally, another thread takes its place. Sequential execution of tasks is guaranteed.

    • NewSingleThreadScheduledExecutor () and newScheduledThreadPool (int corePoolSize)

      Timing or periodic work scheduling, the difference between the two is that the former is a single worker thread, the latter is multi-threaded

    • newWorkStealingPool()

      An internal ForkJoinPool is constructed to process tasks in parallel, using working-stealing algorithms, without any guarantee of order.

      **Fork/Join framework: ** a framework that divides large tasks into several small tasks for parallel execution and finally summarizes each small task to obtain the results of large tasks.

  • Why use thread pools

    Threads are scarce resources. If threads are created without limit, system resources will be consumed. Thread pool can manage threads on behalf of developers.

    So thread pooling can not only reduce resource consumption, but also improve thread manageability.

  • Start a thread using a thread pool

    public class Main{
    	public static void main(String[] args) {
    		ExecutorService newFixThreadPool = Executors.newFixedThreadPool(10);
    		newFixThreadPool.execute(new Runnable() {
    			
    			@Override
    			public void run(a) {
    				// TODO Auto-generated method stub
    				System.out.println("Thread starting from thread pool succeeded"); }}); newFixThreadPool.shutdown(); }}Copy the code
  • Execute Indicates that a new task is executed

    The ThreadPoolExecutor constructor takes several arguments:

    1. CorePoolSize: number of core threads.

    2. MaximumPoolSize: The maximum number of threads that can be created when there are insufficient threads.

    3. WorkQueue: indicates a waiting queue.

    Then the following judgments will be made after the new task is submitted:

    1. If fewer threads are running than corePoolSize, a new thread is created to process the task, leaving the other threads in the instant thread pool idle.

    2. If the number of threads in the pool is greater than or equal to corePoolSize and smaller than maximumPoolSize, new threads will be created to process tasks only when the workQueue is full.

    3. If corePoolSize is set to the same as maximumPoolSize, the size of the created thread pool is fixed. If a new task is submitted and the workQueue is not full, the thread pool is added to the workQueue for processing.

    4. If the number of threads running is greater than or equal to maximumPoolSize, maximumPoolSize, then if the workQueue is full, the task will be processed using the policy specified by the handler.

  • Handler Thread pool saturation policy

    AbortPolicy: Directly throws an exception. Default.

    CallerRunsPolicy: Executes the task with the caller’s thread.

    DiscardOldestPolicy: Discards the most advanced task in the queue and executes the current task.

    DiscardPolicy: Discards tasks directly

    The custom.

  • How is the size of the thread pool selected

    This problem is not a secret, there are articles on every major technology website on the Internet, I will take the most recognized to write

    CPU intensive: Number of threads = number of cores or number of cores +1

    IO intensive: Number of threads = number of CPU cores * (1+ average waiting time/Average working time)

    Of course, this can not completely rely on this formula, more is to rely on the usual experience to operate, this formula is only for reference.

conclusion

This article provides some of the most basic knowledge of Java multithreading and concurrency, suitable for beginners to understand some of the basic knowledge of Java multithreading, if you want to learn more about concurrency can see my other blog “lock”.

Welcome to visit my personal Blog: Object’s Blog