JAVA

Threads: an intro

mossybeach 2024. 7. 9. 22:39

Today we started the basics on threads.

Honestly the lecture was a little hard to follow so I did some of my own reseach (Geeksforgeeks and chatgpt) to cover some holes in my understanding.

I also looked into the Java book hat added a bit more context to the example codes we were using today.

Today's notes are a combination of all three :3


Usually when processing a code we tend to use a single thread - a single brain/ part of CPU.

This usually happens in the main thread, which is executed the main() in our java file.

 

However to promote efficiency and speed we can split the single thread - a single stack to multiple threads/stacks.

 


 two ways to create a multi-thread:

 

1.) Implementing Runnable (interface)

This is a two-parter way of multi-threading. Fist we create a different thread that will execute the beeping noises

public class BeepTesk implements Runnable{

	@Override
	public void run() {
		Toolkit toolkit = Toolkit.getDefaultToolkit();
		for (int i = 0; i < 5; i++) {
			toolkit.beep();
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

 

We use the Runnable interface and override the run() method. Since Runnable is an interface, we have to define what it will do once it's called. This is where we add the action of what the thread will do.


Then in our 'main thread', we first create the beepTesk object then finalise the creation by adding it into a new Thread object. Then we start the executing by using the start() method.

public class BeepPrintExample2 {

	public static void main(String[] args) {
		
		BeepTesk beepTesk = new BeepTesk();
		
		Thread thread = new Thread(beepTesk);
		
		thread.start();
		
		for (int i = 0; i < 5; i++) {
			System.out.println("뿅~");
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
	}

}

Then we action a second process (the for loop printing the beeps) by declaring it as normal.

We used the try/catch exception when we put the Thread to sleep for 0.5s while it 'rests', if the thread that sleep is interrupted it will be caught.


2.) Extending Thread

What it saids on the tin, we extend the Thread class to our own thread.

public class BeepThread extends Thread {
@Override
public void run() {
	Toolkit toolkit = Toolkit.getDefaultToolkit();
	for (int i = 0; i < 5; i++) {
		toolkit.beep();
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
}

Again we override the run() and assign our own twist on what the thread should execute once started.


public class BeepPrintExample4{

public static void main(String[] args){
Thread thread = new BeepThread();
thread.start();

for(int i = 0; i < 5; i++){
System.out.println("띵");
try{Thread.sleep(500);}
	catch(Exception e){}
}
}
}

Instead of inserting the object created above into the thread like our Runnable() example, we instead create the thread itself as a new BeepThread(); This can be done because BeepThread inherited Thread.


Anonymous Thread classes

Sometimes we can create anonymous thread classes for single use threads. Think of anonymous functions in JS in terms of similarity of concept. They are created as 'single-use'. 

 

Example of anon threads for Runnable:

Thread thread = new Thread(new Runnable(){
public void run(){
//code to execute//
}
});

 

Example of anon threads for extend Thread:

Thread thread = new Thread() {
public void run(){
//code to excecute//
}
};

 


Thread Names

Thread names by itself doesn't do much. But they are handy when it comes to methods to figure out what thread is doing what when debugging.

 

Main threads will always have the name 'main', the others follow the format of 'Thread-n', n here being a number.

 

However if we want to change the name, we use setName() method.

thread.setName("thread name");

And if we want to know the name we use getName(). 

thread.getName();

 

setName() and getName() are both Thread class' instance methods, therefore Thread's constructor needs to be present. If not we can use Thread's static method: currentThread() to get the Thread's constructor.

public class ThreadNameEx {

	public static void main(String[] args) {
		
		Thread mainThread = Thread.currentThread();
		System.out.println("프로그램 시작 스레드 이름 : " + mainThread.getName());
		
		ThreadA threadA = new ThreadA();
		System.out.println("작업 스레드 이름: " + threadA.getName());
	}
}

class ThreadA extends Thread {
	public ThreadA() {
		setName("Thread A");
	}
	}
}

Synchronized keyword

When threads share a mutual object, there is a high chance that an error can occur because either thread can accidentally take a value that the other has left behind. 

 

To prevent this, a critical section where only one thread is executed at a time must be set, therefore Java uses synchronized to set this section

Take the example below:

 

1.) We created a Calculator object that the two threads will share to calculate an outcome:

public class Calculator {
	private int memory;

	public int getMemory() {
		return memory;
	}

	public void setMemory(int memory) {
		this.memory = memory;
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + " : " + this.memory);
	}
}

 

2.) We create User 1 & 2, that will be two serperate threads using Calculator:

 

User1:

public class User1 extends Thread {
	private Calculator calculator;

	public void setCalculator(Calculator calculator) {
		// 이름 지정
		setName(" User 1");
		this.calculator = calculator;
	} 

		@Override
		public void run() {
		calculator.setMemory(100);
		}
}

 

User 2:

public class User2 extends Thread {
	private Calculator calculator;

	public void setCalculator(Calculator calculator) {
		setName("User 2");
		this.calculator = calculator;
	}
	
	@Override
	public void run() {
		calculator.setMemory(50);
	}
}

 


Then there is the main thread where it is executed:

Main thread:

public class MainThreadEx {
public static void main(String[] args) {
	Calculator calculator = new Calculator();
	
	User1 user1 = new User1();
	User2 user2 = new User2();
	
	user1.setCalculator(calculator);
	user2.setCalculator(calculator);
	user1.start();
	user2.start();
}
}

In this code, it ends up returning the value of 50, despite user1 taking 100 due to the 2 second delay it went under while executing sleep().


 

In order to prevent this we add the synchronized keyword in Calculator

public synchronized void setMemory(int memory) {
		this.memory = memory;
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + " : " + this.memory);
	}