Java - Basic - Multithreaded Programming

Posted by WolfRage on Thu, 14 Oct 2021 18:23:59 +0200

Java - Basic - Multithreaded Programming

1 Fundamentals

1.1 Process

Generally, operating systems that can execute multiple programs at the same time have the concept of a process, a process is a program in progress, and each process has its own separate space and set of system resources. In the concept of a process, the internal state and data of each process are completely independent.

1.2 Threads

Threads are similar to processes in that they are a piece of code that accomplishes a specific function and a single sequential control process in a program, but unlike processes, multiple threads of the same kind share a single memory space and a set of system resources. Therefore, when a system switches between threads, the overhead is lower than that of processes. Threads are therefore called lightweight processes.
* A process can contain multiple threads

1.3 Main Thread

At least one thread in Java is the main thread, which is created by the JVM after program start and stopped by the JVM after program end. The main thread manages the start, suspend, stop and other operations of the sub-threads.

Get the main thread name sub-case:
The **Thread.currentThread()** method in the code below gets the current thread because the current thread in the main method is the main thread.
The Thread class is a thread class, located in the java.lang package.
The second line of code uses the getName() method to get the name of the main thread.

public class Test{
	public static void main(String args[]){
		Thread mainThread=Thread.currentThread();
		System.out.println("Main thread name:"+mainThread.getName());
	}
}

After the program runs, it appears as main, proving that the name of the main thread is main, created by JVM

main

2 Create Subthread

Creating a child thread in Java involves the java.lang.Thread class and the java.lang.Runnable interface.

For multithreaded implementations, you should use the start() method and the run method. There is no time slice rotation and threads are executed individually

  • Thread is a thread class, creating a Thread object results in a new thread.
  • The program code executed by a thread is written in the run method that implements the Runnable interface, and the object that implements the Runnable interface is the thread execution object
  • The thread execution object is the run method in the Runnable interface. The run method is the entry for the thread execution. The methods to be executed in the thread are all written in the run method. The run method is called the thread body

2.1 Implementing Runnable Interface

When creating a Thread object, you can pass the Thread Execution object to it, which requires two construction methods of Thread:

  • Thread(Runnable target,String name):Target is a thread execution object that implements the Runnable interface. Name is the name specified for the thread
  • Thread(Runnable target): Target is a thread execution object that implements the Runnable interface. Thread names are assigned by the JVM

There are two static methods for thread sleeping

  • static void sleep(long millis): Sleeps the executing thread within a specified millisecond.
  • static void sleep(long millis,int nanos): Sleeps the executing thread within a specified number of milliseconds plus a specified number of nanoseconds.

The code is as follows

public class Runner implements Runnable{
	@Override
	public void run(){
		for(int i=0;i<10;i++){
			System.out.printf("thread%s,No.%d Secondary Execution\n",Thread.currentThread.getName(),i);
			
			try{
				long sleepTime=(long)(Math.random()*1000);
				Thread.sleep(sleepTime);
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread.getName+"Thread End\n")
	}
}
public class HelloWorld{
	Thread t1 = new Thread(new Runner());
	t1.start();

	Thread t2 = new Thread(new Runner(),"MyThread");
	t2.start();
}

2.2 Inherit Thread Thread Thread Class

The Thread class also implements the Runnable interface, so the Thread class can also act as an execution object for threads, which requires inheriting the Thread class and overriding the run method

Note: The following super refers to calling a method of the same name in the parent class, that is, calling the construction method Thread() in the Thread class, and if possible looking at java.lang.Thread.Thread()

public class MyThread extends Thread{
	
	public MyThread(){
		super();
	}

	public MyThread(String name){
		super(name);
	}

	@Override
	public void run(){
		for(int i=0;i<10;i++){
			System.out.printf("thread%s,No.%d Secondary Execution\n",Thread.currentThread.getName(),i);
			
			try{
				long sleepTime=(long)(Math.random()*1000);
				Thread.sleep(sleepTime);
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread.getName+"Thread End\n")
	}
}
public class HelloWorld{

	Thread t1 = new MyThread();
	t1.start();

	Thread t2 = new Thread("MyThread");
	t2.start();
}

2.3 Implementing function bodies using anonymous inner classes and Lambda expressions

Conditions for use: If the threadbody is used sparingly, you can implement the Runnable interface using anonymous inner classes and Lambda expressions instead of defining a single class. Internal classes are recommended here.

  • 2.3.1 Use anonymous internal classes

public class HelloWorld{
	
	public static void main(String args[]){
		Thread t1 = new Thread(new Runnable(){
			
			@Override
			public void run(){
				for(int i=0;i<10;i++){
			System.out.printf("thread%s,No.%d Secondary Execution\n",Thread.currentThread.getName(),i);
			
				try{
					long sleepTime=(long)(Math.random()*1000);
					Thread.sleep(sleepTime);
				}catch(InterruptedException e){
					e.printStackTrace();
				}
				}//End of for loop
				System.out.println(Thread.currentThread.getName+"Thread End\n")
			}
		});
	t1.start();
	}
}

The code above implements the Runnable interface using an anonymous internal class, overriding the run() method. The Thread(Runnable target) construction method is used

  • #####2.3.2 Use lambda expressions
public class HelloWorld{
	public static void main(String args[]){
		
		Thread t2 = new Thread(()->{
			for(int i=0;i<10;i++){
				System.out.printf("thread%s,No.%d Secondary Execution\n",Thread.currentThread.getName(),i);
			
				try{
					long sleepTime=(long)(Math.random()*1000);
					Thread.sleep(sleepTime);
				}catch(InterruptedException e){
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread.getName+"Thread End\n")
		}
	}
}

The above code uses a lambda expression to implement the Runnable interface, overriding the run() method. The Thread(Runnable target,String name) construction method is used. A lambda expression is its first parameter

Status of 3 threads

There are several states in a thread's life cycle

  • New state: The new state creates a thread object, such as a new one, which is just an empty thread object
  • Ready state: When the main thread invokes the start method, the thread enters a ready state (Runnable), at which point the thread has not officially started execution and needs to wait for the cpu to be scheduled
  • Running state: The CPU is scheduled for ready threads, threads enter the running state (Running), running threads, exclusive cpu. Execute the run method
  • Blocked state: For some reason a thread enters an unrunnable state, that is, a blocked state, where the JVM is not executing the thread, even if the cpu is idle
  • Dead state: When a thread exits the run() method, it enters a Dead state. When a thread enters a Dead state, it either dies after running the run() method normally or it may die abnormally.

Note that threads are blocked for several reasons:

  • The current thread calls the sleep() method and goes into hibernation
  • The current thread is called by another thread to wait for the other thread to finish
  • Issue I/O requests while waiting for I/O operations to complete
  • The current thread calls the wait() method

Thread Status Diagram:

4 Thread Management

4.1 Thread Priority

Priority scheduling for threads, Java provides 10 priority levels, expressed as 1-10 integers, the highest priority level is 10, commonly expressed as MAX_PRIORITY, the lowest priority level is 1, commonly expressed as MIN_PRIORITY, and the default priority level is 5, commonly expressed as NORM_PRIORITY.
Thread provides the following ways to set/get priority

  • setPriority(int newpriority): Set priority
  • getPriority(): Get Priority
public class HelloWorld{
	
	public static void main(String args[]){

		Thread t1 = new Thread(new Runnable);
		t1.setPriority(MAX_PRIORITY);
		t1.start();

		Thread t2 = new Thread(new Runnable);
		t2.setPriority(MIN_PRIORITY);
		t2.start();
}

In most cases, t1 executes first, but occasionally t2 executes first, so the order in which threads execute first is not entirely or prioritized, but system-related

4.2 Waiting for thread to end

When the current thread calls the join() method of the t1 thread, it blocks the current thread, waits for t1 to end, or waits for a timeout, the current thread returns to its ready state and waits for a cpu call
join() contains the following construction methods:

  • void join(): Wait for the thread to end
  • void join(long millis): The maximum time to wait for a thread to end is miilis milliseconds. If millis is 0, you need to wait all the time, as above
  • void join(long millis,int naons): The maximum time to wait for a thread to end is millis milliseconds plus naons nanoseconds

The sample code is as follows:

public class HelloWorld{

	static int value=0;

	public static void main(String args[]){

		System.out.println("Main Thread Start...");
		Thread t1 = new Thread(()->{
			System.out.println("ThreadA start...");
			for(int i=0;i<2;i++){
				System.out.println("ThreadA Execution in progress...");
				value++;
			}
			System.out.println("ThreadA End...");
		},"ThreadA");
		t1.start();
        try {
			t1.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.printf("value=%d \n",value);
		System.out.println("End of Main Thread...");	
	}
}
  • As shown in the code above, when using join, the t1 thread blocks the main thread (the current thread), the main thread stops, and waits for t1 to end. The result is as follows:
Main Thread Start...
ThreadA start...
ThreadA Execution in progress...
ThreadA Execution in progress...
ThreadA End...
value=2
 End of Main Thread...

  • As shown in the code above, when no join is used, the result is as follows: Priority independent, the main thread and t1 have priority 5, even if the priority of t1 is set to MAX_PRIORITY, the result is as follows
Main Thread Start...
value=0 
End of Main Thread...
ThreadA start...
ThreadA Execution in progress...
ThreadA Execution in progress...
ThreadA End...

4.3 Thread Concession

Thread also provides a yield() method that stops the current thread from running, abandons CPU resources, and concedes to other threads, similar to the sleep() method, except that yeild() only concedes to threads whose priority is higher than the current thread, while the sleep() method has no priority, meaning that CPU can be used

It is rarely used in practice because the yeild() method cannot control time

4.4 Thread Stop

public class HelloWorld {

	private static String command = ""; 

	public static void main(String[] args) {
		// Create thread t1, using internal class methods
		Thread t1 = new Thread(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				while (!command.equalsIgnoreCase("exit")) { 
					// Threads begin to work
					//Here equalsIgnoreCase is used to determine whether to enter exit, case-insensitive
					// TODO
					System.out.println("Downloading...");
					try {
					// Thread Sleep
					Thread.sleep(1000);
					} catch (InterruptedException e) {
					}
				}
			// End of thread execution
			System.out.println("Execution Complete!");
			}
			
		});

		// Start Thread t1
		t1.start();
		try (InputStreamReader ir = new InputStreamReader(System.in); ③
		BufferedReader in = new BufferedReader(ir)) {
		// A string input was received from the keyboard
			command = in.readLine(); 
		} catch (IOException e) {
		}
	}
}

Execution results as shown in Fig.

Downloading...
Downloading...
Downloading...
exit
 Execution Complete!

5 Thread Security

In a multithreaded situation, thread insecurity may occur if multiple threads need to access a resource

5.1 Critical Resource Problems

Multiple threads run at the same time, sometimes sharing data between threads, and one thread needs data from other threads. Otherwise, the results of program running cannot be guaranteed to be correct.

Example:
For example, if there is an airline selling tickets, the number of tickets per day is limited, and many ticket outlets sell these tickets at the same time. Here is a simulated ticket sales system, the sample code is as follows:

public class TicketDB {

	// Number of tickets
	private int ticketCount = 5; ①

	// Get the current number of tickets
	public int getTicketCount() { ②
		return ticketCount;
	}

	// Sell tickets
	public void sellTicket() { ③
		try {
			// Equal to User Payment
			// Thread sleeps, blocks current thread, simulates waiting for user payment
			Thread.sleep(1000); ④
		} catch (InterruptedException e) {
		}
	System.out.printf("No.%d Number ticket,Already sold\n", ticketCount);
	ticketCount--; ⑤
	}
}

The calling code is as follows:

public class HelloWorld {
	public static void main(String[] args) {
		
		TicketDB db = new TicketDB();
		// Create Thread t1
		Thread t1 = new Thread(new Runnable() {


			@Override
			public void run() {
				// TODO Auto-generated method stub
				while (true) {
					int currTicketCount = db.getTicketCount(); 
					// Query for tickets
					if (currTicketCount > 0) { 
						db.sellTicket(); 
					} else {
						// Exit without ticket
						break;
					}
				}
			}
		});
		// Start Thread t1
		t1.start();
		// Create Thread t2
		Thread t2 = new Thread(() -> {
			while (true) {
				int currTicketCount = db.getTicketCount();
				// Query for tickets
				if (currTicketCount > 0) {
					db.sellTicket();
				} else {
					// Exit without ticket
					break;
				}
			}
		});
		// Start Thread t2
		t2.start();
	}
}

The code seems okay, but when it actually runs, the following problems are found

Ticket 5,Already sold
 Ticket 5,Already sold
 Ticket 3,Already sold
 Ticket 3,Already sold
 Ticket No.1,Already sold
 Ticket No. 0,Already sold

This is the resource critical problem

5.2 Multithreaded Synchronization

To prevent multithreaded access to critical resources from sometimes causing inconsistencies in data, Java provides a "mutex" mechanism that adds a "mutex" to these resource objectsThread synchronization is an important means to ensure thread safety, but it can objectively result in poor performance.
Thread synchronization can be achieved in two ways, both involving the use of the synchronized keyword

  • The synchronized method, which uses the synchronized keyword modifier, synchronizes the methods as follows, which means that the two methods are synchronized, locked, and accessed by only one thread at a time
public class TicketDB {

	// Number of tickets
	private int ticketCount = 5; 

	// Get the current number of tickets
	public synchronized int getTicketCount() { 
		return ticketCount;
	}

	// Sell tickets
	public synchronized void sellTicket() { 
		try {
			// Equal to User Payment
			// Thread sleeps, blocks current thread, simulates waiting for user payment
			Thread.sleep(1000); 
		} catch (InterruptedException e) {
		}
	System.out.printf("No.%d Number ticket,Already sold\n", ticketCount);
	ticketCount--; 
	}
}
  • A synchronized statement that restricts the execution of a piece of code by placing the synchronized keyword in front of the object. This method is mostly used in third-party classes, as follows
public class HelloWorld {
	public static void main(String[] args) {
		
		TicketDB db = new TicketDB();
		// Create Thread t1
		Thread t1 = new Thread(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				while (true) {
					synchronized (db) {
						int currTicketCount = db.getTicketCount(); 
						// Query for tickets
						if (currTicketCount > 0) { 
							db.sellTicket(); 
						} else {
							// Exit without ticket
							break;
						}
					}
				}
			}
		});
		// Start Thread t1
		t1.start();
		// Create Thread t2
		Thread t2 = new Thread(() -> {
			while (true) {
				synchronized(db) {
					int currTicketCount = db.getTicketCount();
					// Query for tickets
					if (currTicketCount > 0) {
						db.sellTicket();
					} else {
						// Exit without ticket
						break;
					}
				}
			}
		});
		// Start Thread t2
		t2.start();
	}
}

6-Thread Communication

For example, there is a classic stack problem where one thread generates some data to stack it; another thread consumes the data and pulls it out of the stack. The two threads depend on each other. When the stack is empty and the consumer thread cannot pull out the data, the generation thread should be notified to add the data; when the stack is full, the production line process cannot add the data, it should be notified to cancelThe cost thread fetches the data.
To solve communication problems in threads, you need to use five methods in the Object class, as follows

  • void wait(): causes the current thread to release the object lock, and then the current object is in a group match state in the object waiting queue, waiting for other threads to wake up
  • void wait(long timeout): As with the wait() method, a timeout is set to wait milliseconds for the timeout to wake up automatically
  • void wait(long timeout,int nanos): With the wait() method, wake up automatically after waiting timeout milliseconds plus nanos nanoseconds
  • void notify(): The current thread wakes this object up waiting for a thread in the queue that is ready
  • void notofyAll(): The current thread wakes this object up and waits for all threads in the queue, which are ready

Figure:


The stack consumption code is as follows:

public class Stack {
	// Initial stack pointer value is 0
	private int pointer = 0;
	// 5-character space on the stack
	private char[] data = new char[5];

	// Stack method with mutex
	public synchronized void push(char c) {
		// The stack is full and cannot be stacked
		while (pointer == data.length) {
			try {
				// Wait until there is data on the stack
				this.wait();
			} catch (InterruptedException e) {
			}
		}
		// Notify other threads to stack data
		this.notify();
		// Data Stack
		data[pointer] = c;
		// Move pointer up
		pointer++;
	}

	// Stack-out method with mutex
	public synchronized char pop() {
		// Stack has no data and cannot be stacked
		while (pointer == 0) {
			try {
				// Waiting for other threads to stack data
				this.wait();
			} catch (InterruptedException e) {
			}
		}
		// Notify other threads of stack
		this.notify();
		// Move pointer down
		pointer--;
		// Data out of stack
		return data[pointer];
	}
}

Call code:

public class HelloWorld {
	public static void main(String args[]) {
		Stack stack = new Stack();
		// The following consumers and producers operate on the same stack object stack
		// Producer Threads
		Thread producer = new Thread(() -> {
			char c;
			for (int i = 0; i < 10; i++) {
				// Randomly generate 10 characters
				c = (char) (Math.random() * 26 + 'A');
				// Stack characters
				stack.push(c);
				// Print Characters
				System.out.println("production: " + c);
				try {
					// Sleep on every character thread generated
					Thread.sleep((int) (Math.random() * 1000));
				} catch (InterruptedException e) {
				}
			}
		});
		// Consumer Threads
		Thread consumer = new Thread(() -> {
			char c;
			for (int i = 0; i < 10; i++) {
				// Read Characters from Stack
				c = stack.pop();
				// Print Characters
				System.out.println("consumption: " + c);
				try {
					// Sleep every character thread read
					Thread.sleep((int) (Math.random() * 1000));
				} catch (InterruptedException e) {
				}
			}
		});
		producer.start(); // Start producer thread
		consumer.start(); // Start consumer thread
	}
}

Topics: Java