java Basics: threads

Keywords: Java

thread

The following content is related to the following figure

As can be seen from the figure, TIMED_WAITING, WAITING and BLOCKED all belong to thread blocking. Their common feature is that threads do not execute code and do not participate in CPU contention. In addition, they also have their own characteristics: (important)
(1) Blocking 1. When the thread is running, it enters this blocking after calling the sleep or join method. The blocking state can be restored to the RUNNABLE state if the thread is interrupted, or the specified time is up, or the thread of the join ends
(2) Blocking 2: when the thread is running, it finds that the lock is unavailable and enters this blocking state. The blocking state can be restored to the RUNNABLE state, provided that the lock object that the thread needs to compete for becomes available (other threads release the lock)
(3) Blocking 3: when the thread is running, after calling the wait method, the thread releases the lock first and then enters this blocking. The blocking state can be restored to the BLOCKED state (that is, blocking 2), provided that the thread is interrupted or awakened by another thread (notify method)

1. Common methods in Thread

1.1 Sleep() method

public static native void sleep(long millis) throws InterruptedException;

effect:
This static method allows the currently executing thread to temporarily sleep for a specified number of milliseconds

As follows:

Example 1:

public class Demo01 {
	public static void main(String[] args) {
		TestThread t = new TestThread();
		t.start();
	}	
}

//Create a thread class
class TestThread extends Thread{

	@Override
	public void run() {
		System.out.println("Before hibernation...");
		try {
			TestThread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("After a period of sleep...");
	}
}

It can be seen that the second sentence is output after a period of time (1 second), which is the function of Sleep method

Example 2:

public class Demo02 {
	public static void main(String[] args) {
		Thread t1 = new Thread("t1 thread ") {
			@Override
			public void run() {
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		};
		
		//Get the status before startup: here is the NEW status
		System.out.println(t1.getState());
		//Start t1 thread
		t1.start();
		for(int i = 0; i<1000; i++) {
			System.out.println(t1.getState());
		}
	}
}

result
NEW
RUNNABLE
RUNNABLE
RUNNABLE
RUNNABLE
RUNNABLE
RUNNABLE
RUNNABLE
RUNNABLE
RUNNABLE
RUNNABLE
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
. . .
TIMED_WAITING
TIMED_WAITING

1.2 join() and join() methods

effect:
Let the current thread block and wait for another specified thread to run before the current thread can continue to run.

1. Nonparametric join method

public class Demo03 {
	public static void main(String[] args) {
		Thread t1 = new Thread("t1 thread "){
			@Override
			public void run() {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("t1 Thread end");
			}
		};
		Thread t2 = new Thread("t2 thread ") {
			@Override
			public void run() {
				try {
					t1.join(); //Let the t2 thread block and wait for t1 to complete t2 before executing
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("t2 Thread end");
			}
		};
		
		t1.start();
		t2.start();
		
		//Let the main thread sleep for a period of time to let t1 and t2 threads enter the state
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println(t2.getState());
	}
}

result
WAITING
t1 thread end
t2 thread end

Reasons for this result:
If the t2 thread calls the join() method of the t1 object, the t2 thread will enter the WAITING state from the RUNNABLE state, and the t2 thread will not run until the t1 thread runs

The human word is that the thread in which the join method appears will be blocked and let the thread of the called object execute first...

2. join() method with parameters

public class Demo03 {
	public static void main(String[] args) {
		Thread t1 = new Thread("t1 thread "){
			@Override
			public void run() {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("t1 Thread end");
			}
		};
		Thread t2 = new Thread("t2 thread ") {
			@Override
			public void run() {
				try {
					//Let t1 thread sleep for 1 second
					t1.join(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("t2 Thread end");
			}
		};
		
		t1.start();
		t2.start();
		
		//Let the main thread sleep for a period of time to let t1 and t2 threads enter the state
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println(t2.getState());
	}
}

result
TIMED_WAITING
t1 thread end
t2 thread end

You can see that after the thread executes the join(long million) method, it will enter the timed state from the RUNNABLE state_ Waiting status.

Summary:

  1. If the nonparametric join() method is called, the thread will be blocked until a certain condition is met. In this case, the state of the thread is WAITING (WAITING indefinitely)
  2. If the join(long million) method with parameters is called, the thread will automatically return to the RUNNABLE state after blocking for a period of time. In this case, the state of the thread is TIMED_WAITING (limited waiting)

1.3 interrupt() method

1.3.1 function

Break a thread

1.3.2 working principle

The interrupt method breaks the blocking state by changing the value of an identifier in the thread object (true|false). When a thread is in the blocking state, it will always monitor whether the value of this ID is true. If it is found that this value becomes true, it will throw an exception to end the blocking state and change this value to false.

1.3.3 about InterruptedException exception

Chinese Name: interrupted exception

background
In fact, the previous two methods, sleep method and join method, may throw InterruptedException type exceptions, indicating that it is possible to throw InterruptedException type exceptions when calling sleep and join to make the thread enter the blocking state.

introduce
InterruptedException exception refers to the interrupted method invoked by thread B in thread A, and the thread B is blocked at this time, then the sleep method or join method throws the interrupt exception.

interrupt essence
It can be seen from the Thread source code that the interrupt method actually calls the interrupt 0 method. The annotation on this method is: Just to set the interrupt flag, which is a local method modified by native. The final result of the interrupt method only changes the value of an identification flag in the Thread object

See the following code for specific demonstration:

public class Demo04 {
	public static void main(String[] args) {
		Thread t1 = new Thread("t1 thread ") {

			@Override
			public void run() {
				try {
					Thread.sleep(10000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			System.out.println("t1 Thread end");
			}
		};
		
		t1.start();
		
		//Let the main thread sleep for 500 milliseconds
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		//Interrupt the blocking state entered by t1 due to calling the sleep method
		t1.interrupt();
		
	}
}

result:
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at blog_xiancheng_2.Demo04$1.run(Demo04.java:10)
t1 thread end

It can be seen that the t1 thread invoked the sleep method into the blocking state, and it needed 100 milliseconds to recover. However, the main thread called the interrupt method of the t1 thread object interrupt. At this point, the Thread.sleep (10000) method throws the interrupted exception, while the t1 thread reverts from the plug state to the RUNNABLE state, continues executing the code, and finally outputs the "t1 thread end".

1.4 methods to view the value of "break ID" in thread object

1.4.1 isInterrupted() method

public boolean isInterrupted() {    
	return isInterrupted(false);
	}
	
private native boolean isInterrupted(boolean ClearInterrupted);

Function introduction

This is a non static method. It only returns the "break ID" value (true/false) and will not clear the value, because the value of the passed parameter ClearInterrupted is false. By default, the interrupt ID value of a thread object is false.

Code demonstration:
Example 1:

public class Demo05 {
	public static void main(String[] args) {
		Thread t1 = new Thread("t1 thread ") {

			@Override
			public void run() {
				for(int i = 0; i<10; i++) {
					//Determine whether other threads have called their own interrupt method
					//Calling a non static method in a class: isInterrupted
					System.out.println(this.isInterrupted()); 
					//Why use this? First of all, this is an anonymous internal class. You can't call it directly with the class name, and one of the functions of this is to replace the class name
				}
				System.out.println("t1 Thread end!");
			}
		};
		t1.start();
	}
}

result:
false
false
false
false
false
false
false
false
false
false
t1 thread end!

**Note: no matter whether the thread is blocked or not, other threads can call the interrupt method of this thread. This method only changes the interrupt ID value (true/false) * *

Example 2:

public class Demo06 {
	public static void main(String[] args) {
		Thread t1 = new Thread("t1 thread ") {

			@Override
			public void run() {
				for(int i = 0; i<10; i++) {
					//Determine whether other threads have called their own interrupt method
					//Calling a non static method in a class: isInterrupted
					System.out.println(this.isInterrupted()); 
				}
				System.out.println("t1 Thread end!");
			}
		};
		//Start thread
		t1.start();
		
		//Interrupt thread
		t1.interrupt();
	}
}

result
true
true
true
true
true
true
true
true
true
true
t1 thread end!

1.4.2 interrupted() method

public static boolean interrupted() {
   	return currentThread().isInterrupted(true);
}

private native boolean isInterrupted(boolean ClearInterrupted);

Function introduction
This is a static method. It returns the interrupt identification value and will clear the value, because the value of the passed parameter ClearInterrupted is true

Example:

public class Demo07 {
	public static void main(String[] args) {
		Thread t1 = new Thread("t1 thread ") {

			@Override
			public void run() {
				for(int i = 0; i<10; i++) {
					//Determine whether other threads have called their own interrupt method
					//Calling a non static method in a class: isInterrupted
					System.out.println(Thread.interrupted()); 
				}
				System.out.println("t1 Thread end!");
			}
		};
		//Start thread
		t1.start();
		
		//Interrupt thread
		t1.interrupt();
	}
}

result:
true
false
false
false
false
false
false
false
false
false
t1 thread end!

It can be seen that after returning true for the first time, the "interrupt ID" value will be checked later in the calling method, which is false, because the value will be cleared directly after the static method interrupted returns true.

Summary: Interrupt() method, isInterrupted() method and interrupted() method look similar, but their functions are different,
1. The Interrupt() method is used to interrupt a thread
2. The isInterrupted() method is used to return the interrupt ID value and will not clear it
3. The interrupted() method is also used to return the interrupt identification value, but this value will be cleared after the interrupt

2. Thread safety and thread synchronization

2.1 thread safety

definition

If there are multiple threads that concurrently access the same variable in the heap over a period of time and have write operations, the data results may eventually be inconsistent with expectations. This is a thread safety problem.

case

First of all, we know that the heap area in the JVM memory is a shared area, which is the memory space that all threads can access. The stack area in JVM memory is the private space of threads. Each thread has its own stack area, and other threads cannot access the data of its own stack area. In a multithreaded environment, if two threads access data in an object in the heap concurrently, the data may be inconsistent with the expected results.

As follows, if two threads access a member attribute in the same object at the same time, the result may be disordered.

public class Demo01 {
	public static void main(String[] args) {
		myData myData = new myData();
		Thread t1 = new Thread("Thread 1") {

			@Override
			public void run() {
				 //Gets the name of the current thread
				String name = Thread.currentThread().getName();
				for(int i = 0; i<10; i++) {
					myData.num = i;
					System.out.println(name + " : " + myData.num);
				}
			}
		};
		Thread t2 = new Thread("Thread 2") {
			
			@Override
			public void run() {
				//Gets the name of the current thread
				String name = Thread.currentThread().getName();
				for(int i = 100; i<2000; i++) {
					myData.num = i; //The num attribute is assigned again in thread 2
				}
			}
		};
		
		t1.start();
		t2.start();
	}
}


class myData{
	int num;
}

result:

Thread 1: 1999
Thread 1: 1
Thread 1: 2
Thread 1: 3
Thread 1: 4
Thread 1: 5
Thread 1: 6
Thread 1: 7
Thread 1: 8
Thread 1: 9

You can see that the output num value of t1 thread in the result of each run is different from the expected value, which is a thread safety problem, as shown in the figure:

When t1 and t2 access concurrently, they compete for the CPU time slice, run the time slice, and then compete for the next time slice again after exiting, that is, t1 and t2 run "intermittently". During this period, t1 thread may once get the time slice to run, assign num to 1, and then exit when the time slice is used up. As a result, t2 thread gets the time slice next time, and assigns the value of num to 1999. Then t1 thread gets the time slice again. It was expected to output 1, but the result is 1999. The core reason is that t1 thread operates the variable num, and then exits after the time slice runs out. t2 first comes and operates the variable num. when t1 thread comes again, this value has been "secretly" modified by t2 thread, so it is inconsistent with the expectation.

Supplement:
The member variable is the heap area in memory. The heap area is the memory space that all threads can access, so the member variable is also shared by all threads.

2.2 thread synchronization

Use background

When multiple threads are used to access the same shared variable, and there are write operations on the variable in the thread, thread safety problems are easy to occur.

definition

The way to realize thread synchronization in Java is to lock the synchronized keyword for the code to be synchronized. The effect of thread synchronization is to lock a piece of code. Only one thread that gets the lock can be qualified to execute. For a thread that does not get the lock, it can only get the lock and run the code after the thread that gets the lock executes the code and releases the lock.

synchronized keyword
synchronized modifier block

The format is as follows:

synchronized (Lock object){
    //Code that operates shared variables. These codes need thread synchronization, otherwise there will be thread safety problems    
    //...
}

Let's take a look at a case, as follows:

public class Demo01 {
	public static void main(String[] args) {
		myData myData = new myData();
		Thread t1 = new Thread("Thread 1") {

			@Override
			public void run() {
				 //Gets the name of the current thread
				String name = Thread.currentThread().getName();
				synchronized(myData) {
					for(int i = 0; i<10; i++) {
						myData.num = i;
						System.out.println(name + " : " + myData.num);
					}
				}
			}
		};
		Thread t2 = new Thread("Thread 2") {
			
			@Override
			public void run() {
				//Gets the name of the current thread
				String name = Thread.currentThread().getName();
				synchronized(myData) {
					for(int i = 0; i<10; i++) {
						myData.num = i;
					}
				}
			}
		};
		
		t1.start();
		t2.start();
	}
}

class myData{
	int num;
}

result:
Thread 1: 0
Thread 1: 1
Thread 1: 2
Thread 1: 3
Thread 1: 4
Thread 1: 5
Thread 1: 6
Thread 1: 7
Thread 1: 8
Thread 1: 9

Now run this program, and the result of each output is the same as expected: the value output by thread 1 is 0 ~ 9 every time

The analysis is as follows:
When there are multiple threads, the synchronized object is used to lock a piece of code in these threads, and these locks use the same lock object. For these locked codes, only one thread that gets the lock can be qualified to execute them at a time. For threads that do not get the lock, the lock can only be released after the thread that gets the lock executes the code, It can take the lock and run the code. In this code, when thread 1 gets the lock, it can enter the locked code block, execute the code, execute a short time slice, and then exit. However, the lock is not released, which means that even if thread t2 grabs the right to use the CPU next time, it cannot run the code because t2 does not get the lock

Speaking of people, only one lock can be executed if you get the lock. If you don't get the lock, you can only grab the lock and execute it after the execution of the lock is completed or the lock is released actively.

At this time, the blocking state of t2 thread is different from the blocking entered by calling sleep or join methods previously learned. This blocking belongs to lock blocking (see the front Figure). The t2 thread cannot recover until another thread releases the lock. If the t2 thread is BLOCKED, the state name returned by calling the getState method of the thread object is: BLOCKED

Look at another case:

public class Demo02 {
	public static void main(String[] args) {
		//Here you create an Object of the Object class and use it as a lock Object
		Object obj = new Object();
		
		//Thread 1
		Thread t1 = new Thread("t1") {

			@Override
			public void run() {
				synchronized(obj) {
					try {
						Thread.sleep(100000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		};
		
		//Thread 2
		Thread t2 = new Thread("t2") {

			@Override
			public void run() {
				synchronized(obj) {
					
				}
			}
		};
		
		t1.start();
		
		//Here, let t main thread rest for 1 second, give t1 thread some time, let him get the lock first, and then go to sleep for 100 seconds
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		t2.start();
		
		//Get the current status of two threads respectively
		System.out.println("t1 Thread status:" + t1.getState());
		System.out.println("t2 Thread status:" + t2.getState());
	}
}

The results are as follows:
t1 thread status: TIMED_WAITING
t2 thread status: BLOCKED

analysis:
t1 thread needs to get the lock object obj to run the locked code block, and t2 thread also needs to get the lock object obj to run the locked code block. There is only one lock object obj, so only one thread in t1 and t2 can get it first and execute the code after getting it. Then the other thread can't get it, and if it can't get it, it will be BLOCKED. At this time, the status of the thread is BLOCKED

synchronized modification method

Modifies a non static method. this is used by default and is not specified separately
Modifies a static method. By default, the Class object of the current Class is used as the lock object, and cannot be specified separately

Case:

public class MyData{    
	private int[] arr = new int[20];    
	//The location where the current data can be stored also indicates the number of elements currently stored    
	private int current;    
	//Add data
	 public void add(int num){        
	 String name = Thread.currentThread().getName();        
	 arr[current] = num;        
	 System.out.println(name+"The value written by the thread this time is"+num+",The value taken after writing is"+arr[current]);        current++;   
	 }
}

public class Test {    
	public static void main(String[] args) {        
		MyData myData = new MyData();        
		Thread t1 = new Thread("t1"){            
		@Override            
		public void run() {                
			for (int i = 0; i < 10; i++) {                    
			myData.add(i);                    
			//The computer runs 10 times. It runs too fast. Let it execute slowly to observe the effect                    
			try {                        
			Thread.sleep(1);                  
			} catch (InterruptedException e) {                        
			e.printStackTrace();                   
			}               
		}           
	}       
};       

	 //t2 add a tab before the name of the thread \ t, which is easy to observe when printing        
	 Thread t2 = new Thread("\tt2"){            
	 @Override            
	 public void run() {                
	 	for (int i = 10; i < 20; i++) {                    
			myData.add(i);                    
	 		//The computer runs 10 times. It runs too fast. Let it execute slowly to observe the effect                    
			try {                        
			Thread.sleep(1);                   
	 		} catch (InterruptedException e) {                        
			e.printStackTrace();                   
			}              
	 	}           
	}       
};       
	t1.start();        
 	t2.start();
 	}
 }

Operation results:

It can be seen that when the add method is executed, the written data is inconsistent with the current data.
When we lock the add method with the synchronized keyword, the result is that the written data is consistent with the current data

public class MyData{    
	private int[] arr = new int[20];    
	//The location where the current data can be stored also indicates the number of elements currently stored    
	private int current;    
	//Add data
	 public synchronized void add(int num){        
	 String name = Thread.currentThread().getName();        
	 arr[current] = num;        
	 System.out.println(name+"The value written by the thread this time is"+num+",The value taken after writing is"+arr[current]);        current++;   
	 }
}

analysis:
The code indicates that the thread that gets the lock object this can enter the add method to execute the code. After the code is executed, the lock will be released. At this time, the lock becomes available. All threads that need the lock will return to the RUNABLE state (they were in the lock blocking state before). These threads will compete for the CPU execution right again. Whoever gets the CPU execution right first will go to get the lock first, Enter the code to execute.

The key to the effect of thread synchronization is to let t1 and t2 threads compete for the same lock object

3. About the wait() method and notify() and notifyAll() methods

These three methods are methods in the Object class

  1. There must be these three methods in any object
  2. Can only be called when the object is a lock object
  3. Can only be called in synchronized code blocks
    In other cases, calling these three methods of an object will report an error!

3.1 wait() method

Function: the thread that gets the lock can release the lock immediately even if the code is not executed.
The following code will help you understand the basic usage of this method

Code 1:

public class Test {    
	public static void main(String[] args) {        
		final Object obj = new Object();       
		Thread t1 = new Thread("t1"){            
		 	@Override            
			 public void run() {                
			 String name = Thread.currentThread().getName();                
				 synchronized (obj){                    
		 		for (int i = 0; i < 10; i++) {                        
		 			System.out.println(name+"thread : i = "+i);                   
				 }               
			 }           
		 }       	
	};        
		 Thread t2 = new Thread("t2"){            
		 @Override
			 public void run() {                
				String name = Thread.currentThread().getName();                
		 		synchronized (obj){                    
		 			for (int j = 10; j < 20; j++) {                        
				 		System.out.println(name+"thread : j = "+j);                   }
		            }           
		        }       
		   };        
		 t1.start();        
		 t2.start();   
		 }
	}

The result of code 1 is:
t1 and t2 threads compete for the same lock object obj, so the running result of the program is: either t1 gets the lock input 09 first, and then t2 gets the lock output 1019, or t2 gets the lock input 1019 first, and then t1 gets the lock output 09

Code two
If you want to release the lock when i=5 in t1 thread, let t2 get the lock to run. In t2 thread, when j=15, release the lock and let t1 get the lock to run

public class Test {    
	public static void main(String[] args) {        
		final Object obj = new Object();       
		Thread t1 = new Thread("t1"){            
		 	@Override            
			 public void run() {                
			 String name = Thread.currentThread().getName();                
				 synchronized (obj){                    
		 		for (int i = 0; i < 10; i++) {                        
		 			System.out.println(name+"thread : i = "+i);    
		 			if(i == 5)
		 				try{
		 					//obj is a lock object. In the synchronization code block, you can call the wait method                                			
		 					//Let the thread that currently gets the lock release the lock immediately
		 					obj.wait();
		 				}catch(InterruptedException e){
							e.printStackTrace();
						}               
				 }               
			 }           
		 }       	
	};        
		 Thread t2 = new Thread("t2"){            
		 @Override
			 public void run() {                
				String name = Thread.currentThread().getName();                
		 		synchronized (obj){                    
		 			for (int j = 10; j < 20; j++) {                        
				 		System.out.println(name+"thread : j = "+j);   
				 		     if(j == 15){
								try{
									obj.wait();
								}catch{InterruptedException e){
								e.printStackTrace();		
								}
				 			}
		            	}    
		            }              
		   	};        
		 t1.start();        
		 t2.start(); 
		 
		    //The main thread sleeps for one second, gives t1 and t2 some time, and waits for them to call the wait method        
		    try {            
		    Thread.sleep(1000);       
		    } catch (InterruptedException e) {            
		    e.printStackTrace();       
		    }        
		    System.out.println("t1 The current status of the thread is:"+t1.getState());        
		    System.out.println("t2 The current status of the thread is:"+t2.getState());
		 }
	}

//Operation results:
t1 thread: i = 0
t1 thread: i = 1
t1 thread: i = 2
t1 thread: i = 3
t1 thread: i = 4
t1 thread: i = 5
t2 thread: j = 10
t2 thread: j = 11
t2 thread: j = 12
t2 thread: j = 13
t2 thread: j = 14
t2 thread: j = 15
The current status of t1 thread is: WAITING
The current status of t2 thread is: WAITING

As you can see, the t1 thread and the t2 thread are not running, but the code is not running, and JVM has not stopped. This is because the current thread releases the lock and then enters the blocking state after calling the wait method of the lock object, and waits for other threads to wake themselves up first. If no other thread wakes itself up, it will be WAITING. So the current situation is that both threads t1 and t2 are blocked and WAITING for others to wake themselves up, so the program doesn't run, but it doesn't end. Call the getState methods of t1 and t2, and the returned state is: WAITING

3.2 notify() method and notifyAll() method

effect

Lock object. notify(), this method can randomly wake up a thread waiting for the specified lock object in the waiting pool to make this thread enter the lock pool. Once the lock is found to be available, the thread entering the lock pool can automatically return to the RUNNABLE state. notifyAll() is the thread that wakes up all lock objects in the waiting pool.

Case:

Modify the above code and add the call of notify method

public class Test {    
	public static void main(String[] args) {        
		final Object obj = new Object();        
		Thread t1 = new Thread("t1"){            
		@Override            
		public void run() {                
			String name = Thread.currentThread().getName();                
			synchronized (obj){                    
		for (int i = 0; i < 10; i++) {                        
			System.out.println(name+"thread : i = "+i);                        
			if(i==5){                            
				try {                                
			//Before releasing the lock object, wake up the thread waiting for obj lock object in the waiting pool                                
			//It means to tell the other party that I'm going to release the lock and you're going to grab it                                
					obj.notify();                                
					obj.wait();                           
				} catch (InterruptedException e) {                                
					e.printStackTrace();                           
				}                       
			}                   
		}                    
			//Finally, wake up again before executing all the code to prevent other threads in the waiting pool from waiting for obj lock object 4
			obj.notify();               
			}           
		}       
	};        
	
	Thread t2 = new Thread("t2"){            
		@Override            
		public void run() {                
			String name = Thread.currentThread().getName();                
			synchronized (obj){                    
				for (int j = 10; j < 20; j++) {                        
					System.out.println(name+"thread : j = "+j);                        
					if(j==15){                            
					try {                                
				//Before releasing the lock object, wake up the thread waiting for obj lock object in the waiting pool                                
				//It means to tell the other party that I'm going to release the lock and you're going to grab it                                
					obj.notify();                                
					obj.wait();                           
					} catch (InterruptedException e) {                                
					e.printStackTrace();                           
				}                       
			}                   
		}                    
				//Finally, before executing all the code, wake up once to prevent the thread in the waiting pool from waiting for the obj lock object
				obj.notify();               
				}           
			}       
		};        
				t1.start();        
				t2.start();        
				//The main thread sleeps for one second, gives t1 and t2 some time, and waits for them to call the wait method        
				try {            
					Thread.sleep(1000);       	
				} catch (InterruptedException e) {            
					e.printStackTrace();       
				}       
				 System.out.println("t1 The current status of the thread is:"+t1.getState());        
				 System.out.println("t2 The current status of the thread is:"+t2.getState());   
			 }
		 }
	 

result:
t1 thread: i = 0
t1 thread: i = 1
t1 thread: i = 2
t1 thread: i = 3
t1 thread: i = 4
t1 thread: i = 5
t2 thread: j = 10
t2 thread: j = 11
t2 thread: j = 12
t2 thread: j = 13
t2 thread: j = 14
t2 thread: j = 15
t1 thread: i = 6
t1 thread: i = 7
t1 thread: i = 8
t1 thread: i = 9
t2 thread: j = 16
t2 thread: j = 17
t2 thread: j = 18
t2 thread: j = 19
The current status of t1 thread is TERMINATED
The current status of t2 thread is TERMINATED

Posted by pido on Sun, 24 Oct 2021 02:13:28 -0700