Java multithreading simple learning summary

Process: Each process has an independent code and data space (process context), and switching between processes will have a large overhead. A process contains 1-n threads. A process is the smallest unit of resource allocation.

Thread: The same type of thread shares code and data space, each thread has an independent running stack and program counter (PC), and the thread switching overhead is small. Thread is the smallest unit of CPU scheduling.

Threads and processes are divided into five phases: creation, ready, running, blocking, and termination.

Multi-processing means that the operating system can run multiple tasks (programs) at the same time.

Multithreading refers to the execution of multiple sequence streams in the same program.

There are generally two types of multithreading in Java, one is to inherit the Thread class, and the other is to implement the Runable interface.

1. Extend the java.lang.Thread class

The method of inheriting the Thread class is a more commonly used one. If you just think of a thread and have no other special requirements, then you can use Thread.

 public class ThreadTest {
    
    
	public static void main(String[] args) {
    
    
		ThreadCustomize mThThreadCustomize1 = new ThreadCustomize("A");
		ThreadCustomize mThThreadCustomize2 = new ThreadCustomize("B");
		
		mThThreadCustomize1.start();
		mThThreadCustomize2.start();
	}
 }
 
 class ThreadCustomize extends Thread{
    
    
	 private String  name;
	 public ThreadCustomize (String name) {
    
    
		 this.name = name;
	 }
	 public void run(){
    
    
		 for(int i = 0; i<5; i++) {
    
    
			 System.out.println( i + ":" + name + "running");
			 try {
    
    
                 Random num = new Random();
				 sleep(num.nextInt(10)*10);
			 }catch (InterruptedException e){
    
    
				 e.printStackTrace();
			 }
		 }
	 }
 }

When the program starts to run main, the Java virtual machine starts a process, and the main thread main is created when main() is called. With the call to the start method of the two objects of ThreadCustomize, the other two threads are also started, so that the entire application runs under multiple threads.

After the start() method is called, the multi-threading is not executed immediately, but the thread becomes runnable (Runnable). When to run is determined by the operating system.

From the results of running the program, it can be found that the multi-threaded program is executed out of order. Therefore, only out-of-order code should be designed to be multithreaded.

The purpose of the Thread.sleep() method call is to prevent the current thread from occupying the CPU resources obtained by the process alone, so as to leave a certain amount of time for other threads to execute.
insert image description here

2. Implement the java.lang.Runnable interface

Using Runnable is also a common way, we only need to rewrite the run method.

public class RunnableTest{
    
    
	public static void main(String[] args){
    
    
		new Thread(new RunnableCustomize("C")).start();
		new Thread(new RunnableCustomize("D")).start();
	}
} 
class RunnableCustomize implements Runnable{
    
    
	private String name;
	public RunnableCustomize(String name) {
    
    
		this.name = name;
	}
	
	public void run(){
    
    
		for (int i = 0; i < 5 ; i++) {
    
    
			System.out.println(i + ":" + name + " Running");
			
			try{
    
    
				Random num = new Random();
				sleep(num.nextInt(10)*10);
			}catch(InterruptedException e){
    
    
				e.printStackTrace();
			}
		}
	}
}

operation result:
insert image description here

The RunnableCustomize class implements the Runnable interface, which makes this class have the characteristics of multi-threading. The run() method is a convention for multithreaded programs. All multi-threaded code is inside the run() method. The Thread class is actually a class that implements the Runnable interface.

When starting multi-threading, you need to construct an object through the Thread class constructor Thread(Runnable target), and then call the start() method of the Thread object to run the multi-threaded code.

In fact, all multi-threaded code is run by running Thread's start () method. Therefore, whether it is to extend the Thread class or implement the Runnable interface to achieve multi-threading, the thread is ultimately controlled through the API of the Thread object.

3. The difference between Thread and Runnable

If a class inherits Thread, it is not suitable for resource sharing. But if the Runable interface is implemented, it is easy to achieve resource sharing.

The advantages of implementing the Runable interface over inheriting the Thread class:

  1. Suitable for multiple threads of the same program code to process the same resource
  2. Can avoid the limitation of single inheritance in java
  3. Increase the robustness of the program, the code can be shared by multiple threads, and the code and data are independent
  4. The thread pool can only be placed in threads that implement Runable or callable classes, and cannot be directly placed in classes that inherit Thread

4. Thread state transition

insert image description here

  1. New state (New): A thread object is newly created.
  2. Ready state (Runnable): After the thread object is created, other threads call the object's start() method. The thread in this state is located in the runnable thread pool, becomes runnable, and waits to obtain the right to use the CPU.
  3. Running state (Running): The thread in the ready state acquires the CPU and executes the program code.
  4. Blocked state (Blocked): The blocked state is that the thread gives up the right to use the CPU for some reason and temporarily stops running. Until the thread enters the ready state, it has the opportunity to go to the running state. There are three types of blocking:
    • Waiting for blocking: The running thread executes the wait() method, and the JVM will put the thread into the waiting pool. (wait will release the lock held)
    • Synchronous blocking: When the running thread acquires the synchronization lock of the object, if the synchronization lock is occupied by other threads, the JVM will put the thread into the lock pool.
    • Other blocking: When a running thread executes the sleep() or join() method, or when an I/O request is issued, the JVM will put the thread in a blocked state. When the sleep () state times out, the join () waits for the thread to terminate or time out, or when the I/O processing is completed, the thread is transferred to the ready state again. (sleep will not release the lock held)
  5. Dead state (Dead): The thread finishes its execution or exits the run() method due to an exception, and the thread ends its life cycle.

5. Thread scheduling

  1. Adjust thread priority: Java threads have priorities, and threads with higher priorities will get more running opportunities.

    The priority of a java thread is represented by an integer, and the value range is 1~10. The Thread class has the following three static constants:

    static int MAX_PRIORITY      //线程可以具有的最高优先级,取值为10
    static int MIN_PRIORITY      //线程可以具有的最低优先级,取值为1
    static int NORM_PRIORITY     //分配给线程的默认优先级,取值为5
    

    The setPriority() and getPriority() methods of the Thread class are used to set and get the priority of the thread respectively.

    Each thread has a default priority. The default priority of the main thread is Thread.NORM_PRIORITY.

    The priority of threads has an inheritance relationship. For example, if thread B is created in thread A, then B will have the same priority as A.

    The JVM provides 10 thread priorities, but none of them map well with common operating systems. If you want the program to be ported to various operating systems, you should only use the following three static constants in the Thread class as priorities, so as to ensure that the same priority uses the same scheduling method.

  2. Thread sleep: The Thread.sleep(long millis) method makes the thread go to the blocked state. The millis parameter sets the time to sleep, in milliseconds. When the sleep is over, it turns to the ready (Runnable) state. sleep() has good platform portability.

  3. Thread waiting: The wait() method in the Object class causes the current thread to wait until other threads call the notify() method or notifyAll() wake-up method of this object. These two wakeup methods are also methods in the Object class, and their behavior is equivalent to calling wait(0).

  4. Thread yield: The Thread.yield() method suspends the currently executing thread object and gives the execution opportunity to threads with the same or higher priority.

  5. Thread join: join() method, wait for other threads to terminate. If the join() method of another thread is called in the current thread, the current thread will enter the blocked state until the other process finishes running, and then the current thread will change from the blocked state to the ready state.

  6. Wake up threads: The notify() method in the Object class wakes up a single thread waiting on the monitor of this object. If all threads are waiting on this object, one of the threads will be chosen to wake up. The choice is arbitrary and occurs when the decision on the implementation is made. A thread waits on an object's monitor by calling one of its wait methods. Until the current thread abandons the lock on this object, the awakened thread can continue to execute. The awakened thread will compete in the usual way with all other threads actively synchronizing on this object. For example: the awakened thread has no reliable privilege or disadvantage in being the next thread to lock this object. A similar method also has a notifyAll(), which wakes up all threads waiting on this object monitor.

6. Description of common functions

  1. sleep(long millis): sleep the currently executing thread (pause execution) within the specified number of milliseconds

  2. join(): wait for t thread to terminate

    How to use: join() is a method of the Thread class. It is called directly after starting the thread, and the function of join() is: "wait for the thread to terminate". What needs to be understood here is that the thread refers to the main thread waiting for the child thread to terminate . That is, the code after the sub-thread calls the join() method can only be executed after waiting for the sub-thread to end.

    Thread t = new Thread();
    t.start();
    t.join();
    

    The advantage of using the join() method: In many cases, the main thread generates and starts sub-threads. If the sub-threads need to perform a lot of time-consuming calculations, the main thread will often end before the sub-threads, but if the main thread finishes processing other transactions Finally, the processing results of the sub-threads need to be used, that is, the main thread needs to wait for the sub-threads to complete before ending. At this time, the join() method is used.

    Without adding the join() method:

     public class ThreadTest {
          
          
    	public static void main(String[] args) {
          
          
    		System.out.println (Thread.currentThread().getName() + " 线程运行开始!");
    		ThreadCustomize mThThreadCustomize1 = new ThreadCustomize("A");
    		ThreadCustomize mThThreadCustomize2 = new ThreadCustomize("B");
    
    		mThThreadCustomize1.start();
    		mThThreadCustomize2.start();
    		System.out.println (Thread.currentThread().getName() + " 线程运行结束!");
    	}
     }
    
     class ThreadCustomize extends Thread{
          
          
    	 private String  name;
    	 public ThreadCustomize (String name) {
          
          
    		 super(name);
    		 this.name = name;
    	 }
    	 public void run(){
          
          
    		 System.out.println (Thread.currentThread().getName() + " 线程运行开始!");
    		 for(int i = 0; i<5; i++) {
          
          
    			 System.out.println(name + " running  "  + i);
    			 try {
          
          
                    Random num = new Random();
    				sleep(num.netInt(10)*10);
    			 }catch (InterruptedException e){
          
          
    				 e.printStackTrace();
    			 }
    		 }
    		 System.out.println (Thread.currentThread().getName() + " 线程运行结束!");
    	 }
     }
    

    operation result:
    insert image description here

    It was found that the main thread may end before the child thread ends.

    Add join() method:

    import java.lang.Thread ;
    import java.util.Random;
    
     public class ThreadTest {
          
          
    	public static void main(String[] args) {
          
          
    		System.out.println (Thread.currentThread().getName() + " 线程运行开始!");
    		ThreadCustomize mThThreadCustomize1 = new ThreadCustomize("A");
    		ThreadCustomize mThThreadCustomize2 = new ThreadCustomize("B");
    
    		mThThreadCustomize1.start();
    		mThThreadCustomize2.start();
    		try{
          
          
    			mThThreadCustomize1.join();
    		}catch (InterruptedException e){
          
          
    			e.printStackTrace();
    		}
    		try{
          
          
    			mThThreadCustomize2.join();
    		}catch (InterruptedException e){
          
          
    			e.printStackTrace();
    		}
    		System.out.println (Thread.currentThread().getName() + " 线程运行结束!");
    	}
     }
    
     class ThreadCustomize extends Thread{
          
          
    	 private String  name;
    	 public ThreadCustomize (String name) {
          
          
    		 super(name);
    		 this.name = name;
    	 }
    	 public void run(){
          
          
    		 System.out.println (Thread.currentThread().getName() + " 线程运行开始!");
    		 for(int i = 0; i<5; i++) {
          
          
    			 System.out.println(name + " running  "  + i);
    			 try {
          
          
    				 Random num = new Random();
    				 sleep(num.nextInt(1000));
    			 }catch (InterruptedException e){
          
          
    				 e.printStackTrace();
    			 }
    		 }
    		 System.out.println (Thread.currentThread().getName() + " 线程运行结束!");
    	 }
     }
    

    operation result:
    insert image description here

    The main thread will end after the child thread.

  3. yield(): Suspends the currently executing thread object and executes other threads.

    What yield() should do is return the currently running thread to a runnable state to allow other threads of the same priority to get a chance to run. Therefore, the purpose of using yield() is to allow appropriate round-robin execution between threads of the same priority. However, in practice, there is no guarantee that yield() will fail to yield, because the yielded thread may be selected again by the thread scheduler.

    yield() never caused the thread to go to wait, sleep, block state. In most tree cases, yeild() will cause the thread to go from running to runnable, but may have no effect.

    public class YieldTest{
          
          
    	public static void main(String[] args){
          
          
    		Thread mThreadCustomize1 = new ThreadCustomize ("A");
    		Thread mThreadCustomize2 = new ThreadCustomize ("B");
    		
    		mThreadCustomize1.start();
    		mThreadCustomize2.start();
    	}
    }
    class ThreadCustomize extends Thread{
          
          
    	private String  name;
    	public ThreadCustomize(String name){
          
          
    		super(name);
    	}
    	@Override
    	public void run(){
          
          
    		for(int i = 0; i <50; i++){
          
          
    			System.out.println("" + this.getName() + "=====" + i);
    			if (i == 30){
          
          
    				this .yield();
    			}
    		}
    	}
    }
    

    operation result:

    • The first case: A thread will lose CPU time when it executes to 30, and then B thread grabs CPU time and executes it.
    • The second situation: when thread A executes to 30, it will lose CPU time. At this time, thread A grabs CPU time and executes it.

    The difference between sleep() and yield():

    ​ sleep() makes the current thread enter the stop state, so the thread executing sleep() will definitely not be executed within the specified time; yield() just makes the current thread return to the executable state, so the thread executing yield() It is possible to be executed immediately after entering the executable state.

    The sleep() method makes the currently running thread sleep for a period of time and enters the non-running state. The length of this period is set by the program. The yield() method makes the current thread give up the CPU possession, but the time given up is not allowed set. In fact, the yield() method corresponds to such an operation: first check whether there is currently a thread with the same priority in the runnable state, if so, give the CPU possession right to this thread, otherwise, continue to run the original thread. So the yield() method is called "give in", and it gives up the running opportunity to other threads of the same priority.

    ​ In addition, the sleep() method allows lower-priority threads to get the chance to run, but when the yield() method is executed, the current thread is still in the runnable state, so it is impossible for the lower-priority thread to obtain CPU ownership. In a running system, if a higher-priority thread does not call the sleep() method and has not received I/O blocking, then the lower-priority thread can only wait for all higher-priority threads to finish running. have a chance to run.

  4. setPriority(): Change the priority of a thread

    MIN_PRIORITY = 1

    NORW_PRIORITY = 5

    MAX_PRIORITY = 10

    usage:

    ThreadCustomiza t1 = new ThreadCustomiza("t1");
    ThreadCustomiza t2 = new ThreadCustomiza("t2");
    t1.setPriority(Thread.MAX_PRIORITY);
    t2.setPriority(Thread.MIN_PRIORITY);
    
  5. Interrup(): Instead of interrupting a certain thread, it just sends an interrupt signal to the thread, so that the thread can throw it while waiting indefinitely (such as in a deadlock), thus ending the thread, but if you eat this exception, Then this thread will still not be interrupted.

  6. wait()

    Obj.wait() and Obj.notify() must be used together with synchronized (Obj), that is, wait and notify operate on the Obj lock that has been acquired. From a grammatical point of view, it is Obj.wait(), Obj.notify Must be within the synchronize(Obj){…} statement block. In terms of function, wait means that after the thread acquires the object lock, it actively releases the object lock, and at the same time, the thread sleeps. Until other threads call the object's notify() to wake up the thread, the object lock can be acquired and the execution can continue. The corresponding notify() is the wake-up operation of the object lock. But one thing to note is that after notify() is called, the object lock is not released immediately, but after the execution of the corresponding synchronize(){} statement block is completed, after the lock is automatically released, the JVM will lock the object in wait() Randomly select a thread among the threads, give it an object lock, wake up the thread, and continue execution. This provides synchronization and wake-up operations between threads. Both Thread.sleep() and Object.wait() can suspend the current thread and release the control of the CPU. The main difference is that Object.wait() releases the control of the object lock while releasing the CPU.

    Example: Create three threads, thread A prints A 10 times, thread B prints B 10 times, thread C prints C 10 times, requires threads to run at the same time, and prints ABC 10 times alternately.

    public class MyThreadPrinter2 implements Runnable {
          
             
    	  
        private String name;   
        private Object prev;   
        private Object self;   
      
        private MyThreadPrinter2(String name, Object prev, Object self) {
          
             
            this.name = name;   
            this.prev = prev;   
            this.self = self;   
        }   
        @Override  
        public void run() {
          
            
            int n = 10;
            for (int i = 0; i < n; i++) {
          
          
                synchronized (prev) {
          
          
                    synchronized (self) {
          
          
                        System.out.print(name);
                        //唤醒本对象的监视器上的等待线程
                        self.notify();
                    }
                    try {
          
          
                        if (i < n-1)
                            //如果不是最后一次循环,则将该线程挂到prev对象的监视器上,进行等待...
                            prev.wait();
    						//否则,最后一次循环不能再wait()了,不然就会死锁。
                    }catch (InterruptedException e) {
          
          
                        e.printStackTrace();
                    }
                }
            }
        }   
        public static void main(String[] args) throws Exception {
          
             
            Object a = new Object();   
            Object b = new Object();   
            Object c = new Object();   
            MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);   
            MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);   
            MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);   
               
            new Thread(pa).start();
            Thread.sleep(100);  //确保按顺序A、B、C执行
            new Thread(pb).start();
            Thread.sleep(100);  
            new Thread(pc).start();   
            Thread.sleep(100);  
            }   
    }  
    

    Idea: From a general perspective, the problem is the synchronous wake-up operation among the three threads. The main purpose is that ThreadA->ThreadB->ThreadC->ThreadA executes three threads in a loop. In order to control the order of thread execution, it is necessary to determine the order of waking up and waiting, so each thread must hold two object locks at the same time to continue execution. An object lock is prev, which is the object lock held by the previous thread. Another is the self-object lock. The main idea is that in order to control the order of execution, the prev lock must be held first, that is, the previous thread must release its own object lock, and then apply for its own object lock, print when both are available, and then first call self.notify( ) releases its own object lock, wakes up the next waiting thread, and then calls prev.wait() to release the prev object lock, terminates the current thread, and wakes up again after the waiting loop ends. Run the above code, you can find that three threads print ABC cyclically, a total of 10 times. The main process of program running is that A thread runs first, holds C and A object locks, then releases A and C locks, and wakes up B. Thread B waits for A lock, then applies for B lock, then prints B, then releases B, A lock, wakes up C, thread C waits for B lock, then applies for C lock, then prints C, then releases C, B locks, and wakes up A. It seems that there is no problem, but if you think about it carefully, you will find that there is a problem. It is the initial condition. The three threads are started in the order of A, B, and C. According to the previous thinking, A wakes up B, B wakes up C, C wakes up A again. But this assumption depends on the order of thread scheduling and execution in the JVM.

    The difference between wait and sleep:

    common ground:

    1. They are all in a multi-threaded environment, and they can block the specified number of milliseconds when the program is called, and return.

    2. Both wait() and sleep() can interrupt the suspended state of the thread through the interrupt() method, so that the thread immediately throws InterruptedException.

      If thread A wants to end thread B immediately, it can call the interrupt method on the Thread instance corresponding to thread B. If this thread B is waiting/sleep/joining, thread B will throw InterruptedException immediately, and return directly in catch(){} to safely end the thread.

      It should be noted that InterruptedException is thrown from the thread itself, not by the interrupt() method. When interrupt() is called on a thread, if the thread is executing normal code, then the thread will not throw InterruptedException at all. However, once the thread enters wait()/sleep()/join(), InterruptedException will be thrown immediately.

    difference:

    1. Methods of the Thread class: sleep().yield, etc.

      Object methods: wait() and notify(), etc.

    2. Each object has a lock to control synchronized access. The Synchronize keyword can interact with the object's lock to achieve thread synchronization.

      The sleep method does not release the lock, but the wait method releases the lock so that other threads can use the synchronization control block or method.

    3. wait(), notify() and notifyAll() can only be used in a synchronous control method or a synchronous control block, while sleep can be used anywhere

      So the biggest difference between the sleep() and wait() methods is:

      ​ When sleep() sleeps, keep the object lock and still occupy the lock

      ​ When wait() sleeps, release the object lock

      But both wait() and sleep() can interrupt the suspended state of the thread through the interrupt() method, so that the thread immediately throws InterruptedException (but this method is not recommended).

    sleep () method: make the current thread enter a stagnant state, and give up the use of the CPU. The purpose is to prevent the current thread from occupying the CPU resources obtained by the process alone, and leave a certain amount of time for other threads to execute. sleep() is a Static method of the Thread class, so it cannot change the machine lock of the object, so when the sleep() method is called in a Synchronized block, although the thread sleeps, the machine lock of the object is not released, and other threads Cannot access this object (object lock is held even if stopped). After the sleep() sleep time expires, the thread may not execute immediately, because other threads may be running and not scheduled to give up execution, unless this thread has a higher priority.

    wait() method: a method belonging to the Object class; when a thread executes the wait() method, it enters a waiting pool related to the object, and at the same time releases the machine lock of the object (temporarily losing the machine lock, wait(long timeout) the object lock needs to be returned after the timeout); other threads can access; wait() uses notify or notifyAll or specifies the sleep time to wake up the threads in the current waiting pool. wait() must be placed in a Synchronized block, otherwise a "java.lang.IllegalMonitorStateException" exception will be thrown when the program is running.

7. Explanation of common thread names

Main thread: the thread generated by the JVM calling program main()

Current thread: Generally refers to through Thread. currentThread() to get the process.

Background thread: refers to a thread that provides services for other threads, also known as a daemon thread. The garbage collection thread of the JVM is a background thread. The difference between user threads and daemon threads is whether to wait for the main thread to end depending on the end of the main thread.

Foreground thread: It refers to the thread that accepts the service of the background thread. In fact, the threads behind the foreground are linked together, just like the relationship between the puppet and the manipulator behind the scenes. The puppet is the foreground thread, and the behind-the-scenes manipulator is the background thread. Threads created by foreground threads are also foreground threads by default. You can use the isDaemon() and setDaemon() methods to determine and set whether a thread is a background thread.

Common methods of thread class:

method effect
sleep() Force a thread to sleep for n milliseconds
isAlive() Determine whether a thread is alive
join() wait for thread to terminate
activeCount() The number of active threads in the program
enumerate() Enumerates threads in a program
currentThread() get the current thread
isDaemon() Whether a thread is a daemon thread
setDaemon() Set a thread as a daemon thread
setName() set a name for the thread
wait() force a thread to wait
notify() Notify a thread to continue running
setPriority() Set the priority of a thread

8. Thread synchronization

  1. The Synchronized keyword has two functions:
    • In an object instance, Synchronized aMethod(){} can prevent multiple threads from accessing the synchronized method of this object at the same time (if an object has multiple synchronized methods, as long as one thread accesses one of the synchronized methods, other threads cannot simultaneously access any synchronized method in this object). At this time, the synchronized methods of different object instances do not interfere with each other. In other words, other threads can still access the synchronized method in another object instance of the same class at the same time.
    • It is the range of a certain type, synchronized static aStaticMethod{} prevents multiple threads from accessing the synchronized static method in this class at the same time. It works on all object instances.
  2. In addition to using the synchronized keyword before the method, the synchronized keyword can also be used in a certain block in the method, indicating that only the resources of this block are mutually exclusive. Usage: synchronized(this){/*block*/}, its scope is the current object.
  3. The synchronized keyword cannot be inherited, that is to say, the method synchronized f() {} of the base class is not automatically synchronized f() {} in the inherited class, but becomes f() {}. Inheritance requires you to explicitly designate one of its methods as a synchronized method.

Summarize:

in conclusion:

  1. The purpose of thread synchronization is to protect resources from being damaged when multiple threads access a resource.
  2. The thread synchronization method is implemented through locks. Each object has only one lock. This lock is associated with a specific object. Once a thread acquires the object lock, other threads that access the object can no longer access other threads of the object. asynchronous method
  3. For static synchronization methods, the lock is for this class, and the lock object is the Class object of this class. The locks of static and non-static methods do not interfere with each other. A thread acquires a lock, and when accessing a synchronized method on another object in a synchronized method, both object locks are acquired.
  4. For synchronization, it is the key to always be aware of which object to synchronize on.
  5. To write a thread-safe class, you need to always pay attention to making correct judgments on the logic and security of multiple threads competing to access resources, analyze "atomic" operations, and ensure that other threads cannot access competing resources during atomic operations.
  6. When multiple threads wait for an object lock, the thread that does not acquire the lock will block.
  7. Deadlock is caused by threads waiting for each other for locks, and the probability of occurrence in practice is very small.

Guess you like

Origin blog.csdn.net/qq_43880417/article/details/120569678
Recommended