Multithreading in Java. Overview of Runnable, Thread, Object.

Многозадачность

Two basic entities represent multithreading in Java. There are processes and threads. Java uses threads more often in multithreading programming. However, there are multi-process applications in Java as well.

Processes in Java

A process is a self-contained program with an execution environment. Each process has its own memory space. Often the term process refers to an entire application.

Communication between processes happens by IPC resources: pipelines or sockets.

To create a multi-process application, use the ProcessBuilder class.

Threads in Java

Thread – often referred to as a lightweight process. Like processes, threads provide an environment for performing operations. The only difference is that when creating a stream, you need fewer resources. One process has at least one thread. All threads exist within processes.

Overview of Runnable

Runnable is an interface containing a run method. As part of the execution of instructions, all operations in the run method will perform on a new thread.

Overview of Thread

The primary purpose of the class is to implement a new thread for executing instructions. The Thread class implements the Runnable interface, where all operations are performed on a new thread in the run method. In turn, the class encapsulates many additional fields and flow metadata: name, priority, execution flag in the background. The class also contains many methods that help wait for the completion of a thread, suspend, continue execution of instructions, and many other functional methods.

Method Sleep

It’s a static native method that pauses the execution of the thread. The method argument specifies the number of milliseconds.

Thread.sleep(1500); 		//wait for one and half second
Thread.sleep(2000, 100);  	//wait 2 seconds and 100 nanoseconds

Method Yield

A static method that instructs the processor to switch to other threads. This method effectively waits for events and other cases where the current thread does not require CPU time.

Method Join

This method waits for the execution of a third-party thread. The method’s parameter is the interval during which the delay will occur.

    public final synchronized void join(long millis) throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0L;
        if (millis < 0L) {
            throw new IllegalArgumentException("timeout value is negative");
        } else {
            if (millis == 0L) {
                while(this.isAlive()) {
                    this.wait(0L);
                }
            } else {
                while(this.isAlive()) {
                    long delay = millis - now;
                    if (delay <= 0L) {
                        break;
                    }

                    this.wait(delay);
                    now = System.currentTimeMillis() - base;
                }
            }

        }
    }

As the source code of the library shows, the join method works based on the wait functionality from the Object class.

Object class methods for working with multithreading

The Object class contains three methods for working with threads. These are: wait, notify, notifyAll methods. Next, we’ll take a closer look at how they interact.

Method Wait

This method stops the current thread to wait for notifications from the outside. If the method is called without parameters, the thread waits until the notification is called, otherwise the process waits until the specified time in the numeric parameter in milliseconds.

Method Notify

Notifies one of the threads to stop waiting.

Method NotifyAll

Notifies all threads to stop waiting.

Consideration in practice

In the given example, we see a simple application that waits for notifications in a specific thread. The result may be a little surprising, because we call the notify method instead of notifyAll.

public class Message {
    private String msg;
     
    public Message(String str){
        this.msg=str;
    }
 
    public String getMsg() {
        return msg;
    }
 
    public void setMsg(String str) {
        this.msg=str;
    }
 
}
public class Waiter implements Runnable{
     
    private Message msg;
     
    public Waiter(Message m){
        this.msg = m;
    }
 
    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        synchronized (msg) {
            try{
                System.out.println(name + " waiting for notify: " + System.currentTimeMillis());
                msg.wait();
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(name + " the notify has been called: " + System.currentTimeMillis());
            
            System.out.println(name + " : " + msg.getMsg());
        }
    }
}
public class Notifier implements Runnable {
 
    private Message msg;
     
    public Notifier(Message msg) {
        this.msg = msg;
    }
 
    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        System.out.println(name + " стартовал");
        try {
            Thread.sleep(1000);
            synchronized (msg) {
                msg.setMsg(name + " thread Notifier is done");
                msg.notify();
                // msg.notifyAll();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class WaitNotifyTest {
 
    public static void main(String[] args) {
        Message msg = new Message("обработать");
        Waiter waiter = new Waiter(msg);
        new Thread(waiter,"waiter").start();
         
        Waiter waiter1 = new Waiter(msg);
        new Thread(waiter1, "waiter1").start();
         
        Notifier notifier = new Notifier(msg);
        new Thread(notifier, "notifier").start();
        System.out.println("All threads have started");
    }
 
}
waiter waiting for notify: 12543137440105
waiter1 waiting for notify: 1253313744106
All threads have started
notifier started
waiter notify has been called: 1356318735011
waiter : thread Notifier is done


This is the result when the notifyAll method is called:

waiter waiting for notify: 12543137440105
waiter1 waiting for notify: 1253313744106
All threads have started
notifier started
waiter notify has been called: 1356318735011
waiter : thread Notifier is done
waiter1 notify has been called: 1356318735011
waiter1 : thread Notifier is done

We can see that both pending threads have completed.

Stopping of threads

Often threads use loops with functional instructions, which can be terminated by setting a key flag. Below we give an example of a simple program where a certain thread ends when the mFinish flag is turned on.

class Incremenator extends Thread
{
	private volatile boolean mIsIncrement = true;
	private volatile boolean mFinish = false;

	public void changeAction()	
	{
		mIsIncrement = !mIsIncrement;
	}
	public void finish()		
	{
		mFinish = true;
	}

	@Override
	public void run()
	{
		do
		{
			if(!mFinish)	
			{
				if(mIsIncrement)	
					Program.mValue++;	
				else
					Program.mValue--;	
				
				
				System.out.print(Program.mValue + " ");
			}
			else
				return;		//Завершение потока

			try{
				Thread.sleep(1000);		
			}catch(InterruptedException e){}
		}
		while(true); 
	}
}

public class Program
{
	
	public static int mValue = 0;
	
	static Incremenator mInc;	

	public static void main(String[] args)
	{
		mInc = new Incremenator();	
		
		System.out.print("Значение = ");
		
		mInc.start();	
		
		
		for(int i = 1; i <= 3; i++)
		{
			try{
				Thread.sleep(i*2*1000); //Ожидание в течении i*2 сек.
			}catch(InterruptedException e){}
			
			mInc.changeAction();	
		}
		
		mInc.finish();	
	}
}
Console:
Value = 1 2 1 0 -1 -2 -1 0 1 2 3 4

Terminating of threads

By calling the interrupt method, you can force the execution of a thread to be interrupted. In this case, an InterruptedException will be thrown.

An example of code that interrupts the execution of a thread:

class Incremenator extends Thread
{
	private volatile boolean mIsIncrement = true;

	public void changeAction()	
	{
		mIsIncrement = !mIsIncrement;
	}

	@Override
	public void run()
	{
		do
		{
			if(!Thread.interrupted())	
			{
				if(mIsIncrement) Program.mValue++;	
				else Program.mValue--;			
				
				
				System.out.print(Program.mValue + " ");
			}
			else
				return;		

			try{
				Thread.sleep(1000);		
			}catch(InterruptedException e){
				return;	
			}
		}
		while(true); 
	}
}

class Program
{
	
	public static int mValue = 0;
	
	static Incremenator mInc;	

	public static void main(String[] args)
	{
		mInc = new Incremenator();	
		
		System.out.print("Значение = ");
		
		mInc.start();	
		
		
		for(int i = 1; i <= 3; i++)
		{
			try{
				Thread.sleep(i*2*1000);		
			}catch(InterruptedException e){}
			
			mInc.changeAction();	
		}
		
		mInc.interrupt();	
	}
}

Thread priorities

When executing threads, you can set the priority of execution, which determines the amount of resources provided for the execution of instructions. Available values ​​are MIN_PRIORITY, NORM_PRIORITY and MAX_PRIORITY

Conclusion

Java, like other programming languages, provides many opportunities to execute code at the same time. In this article, we covered the basics of multithreading and multi-processor applications.

Related posts

Leave a Comment