본문 바로가기
IT/Java

[Java Study] 10일차 Thread, Runnable

by Dblog 2021. 1. 17.
728x90

학습할 것

  • Thread 클래스와 Runnable 인터페이스
  • 쓰레드의 상태
  • 쓰레드의 우선순위
  • Main 쓰레드
  • 동기화
  • 데드락

처음으로 API문서를 보면서 공부해봤는데 class의 경우에는 내부에 포함하고 있는 method, emun 등 기본정보는 당연하게 포함하고 있고 

있을거라고 생각하지 않았던 implements 인터페이스를 상속받아 사용하는 부분들 까지 설명이 되어있어 은근히 공부하기 편했습니다. 

 

Thread 클래스와 Runnable 인터페이스

Thread, Runnable? 뭐지?

프로그램에서 프로세스는 프로그램의 작업 단위로 사용되는데 기본 원칙은 프로세스를 점유하는 작업은 작업이 끝날때까지 다른 작업이 프로세스를 사용할 수 없게 하는것이 원칙입니다.

하지만 Thread를 사용하면 프로세스를 공유하며 사용할 수 있습니다. 실질적으로는 점유를 빠르게 빠르게 해서 공유하는 것 처럼 보이게 하겠지만, 사용자가 사용하는 환경에서는 공유하는 것 처럼 보입니다.

public class ThreadTest extends Thread{
	int min;
	
	public ThreadTest(int min) {
		this.min = min;
	}
	@Override
	public void run() {
		try {
		for (int i=0 ;i < min; i++) {
			System.out.println(i);
			sleep(1000);
		}	
		} catch (Exception e) {
		}
	}
	
	public static void main(String[] args) {
		ThreadTest test = new ThreadTest(3);
		ThreadTest test2 = new ThreadTest(5);
		ThreadTest test3 = new ThreadTest(10);
		test.start();
		test2.start();
		test3.start();
	}
}

만약 Thread를 사용하지 않는 코드였다면 test.start()가 모두 실행되고 test2, test3 로 넘어가지만 Thread를 사용했기 때문에 

0
0
0
1
1
1
2
2
2

test, test2, test3가 모두 같이 돌아가게 됩니다. 

 

Thread 클래스와 Runnable의 차이중 가장 눈에 보이는 차이는 Thread는 extends를 사용하고 Runnable은 Implements를 사용한다는 것 입니다. 

상속 방식의 차이와 같이 생기는 차이는 Runnable에는 start()메소드가 따로 없기 때문에 Runnable을 상속받은 클래스는 따로 Thread로 다시 초기화 시켜줘야 start()메소드를 사용할 수 있습니다. 

상속 방식 차이에 대해 Thread를 사용할때 따로 상속받을 부모 클래스가 있다면 Runnable을 사용하고 딱히 없다면 Thread를 상속받는 방법으로 사용할 수 있을것 같습니다. 

 

※ 궁금해서 API 문서를 찾아보긴 했는데

  • Thread

https://docs.oracle.com/javase/8/docs/api/

Thread 클래스에는 이미 Runnable을 상속하고 있는것 같습니다. 

 

  • Runnable

https://docs.oracle.com/javase/8/docs/api/

앞서 봤던 내용대로 Runnable 인터페이스는 Thread에 상속되어 있음을 알 수 있습니다.

 

 

쓰레드의 상태

Thread는 6개의 상태가 있고 각 상태는 getState() 메소드를 통해서 얻어낼 수 있습니다.

https://docs.oracle.com/javase/8/docs/api/

 

코딩을 하면서 뭔가 Enum으로 선언이 되어있나.?? 라는 의문을 가지고 클래스를 열어봤는데

역시나 Java Enum으로 선언되어 있는걸 확인할 수 있었습니다. 

https://docs.oracle.com/javase/8/docs/api/

 

Enum 이라서 getState()를 사용해도 그냥 출력될거 같은데 확인을 해보고 싶어서 간단하게 작성해봤습니다.

public class ThreadTest extends Thread{
	int min;
	
	public ThreadTest(int min) {
		this.min = min;
	}
	@Override
	public void run() {
		try {
		for (int i=0 ;i < min; i++) {
			System.out.println(i + " : ");
			sleep(1000);			
			}
		} catch (Exception e) {
		}
	}
	
	public static void main(String[] args) {
		ThreadTest test = new ThreadTest(3);
		ThreadTest test2 = new ThreadTest(5);
		ThreadTest test3 = new ThreadTest(10);
		
		System.out.println(test.getState());
		test.start();
		System.out.println(test.getState());
		test.stop();
		System.out.println(test.getState());

	}
}
NEW
RUNNABLE
0 : 
TIMED_WAITING

예상했던 대로 출력은 되긴 합니다. 

 

 

쓰레드의 우선순위

모든 Thread는 우선순위를 가지게 되며 우선순위가 높은 스레드 부터 실행됩니다. 처음에 Thread가 만들때 따로 설정하지 않으면 동일한 우선순위에서 스레드가 실행됩니다. 

※ 처음생성되는 쓰레드는 기본값으로 5의 우선순위 값을 가지게 됩니다.

setPriority() 메소드를 통해서 우선순위는 설정할 수 있고 getPriority()는 해당 Thread의 우선순위 정보를 얻을수 있습니다. 

https://docs.oracle.com/javase/8/docs/api/

 

 

Main 쓰레드

main Thread, main method 

JVM이 실행되면 main Thread가 디폴트로 선언 되며 데몬 쓰레드 라고 부르기도 합니다. main Thread는 main method의 작업이 끝날때까지 동작합니다.

public static void main(String[] args) {
	// main 메소드가 실행될때 Main Thread가 시작됩니다.
}

※ main 쓰레드 또한 기본 우선순위 값 5를 가집니다.

 

 

동기화

(동기화에 대한 이슈는 개발하는 프로그램, 데이터 처리 방식에 따라 중요하지 않을수도 있는 이슈입니다.)

가장 많은 예시는 은행 시스템입니다.

멀티 스레드 시스템이 작동중일때

10,000원이 있는 홍길동 계좌에 대하여 동시 작업이 진행될 때의 예시입니다. 

1. 쓰레드 1이 홍길동 계좌에 잔액을 9,000원 인출합니다.
2. 동시에 쓰레드 2가 홍길동 계좌에 잔액을 3,000원 인출합니다.

위 예시의 경우 2번 작업이 진행될때 잔고부족으로 예외를 발생시켜야 합니다. 그렇다면 쓰레드 2가 작업을 시도할때 홍길동 잔고에 3,000원 이상 있는지 확인하는 작업이나 1번 작업이 끝났을때 작업한 결과를 계좌에 적용시키는 행동이 필요합니다. 

행동이 동기화 작업입니다. 

동기화 작업은 synchronized 키워드를 사용하며 synchronized 함수 내에서 동기화 할 작업을 선언하게 됩니다. 

// https://www.tutorialspoint.com/java/java_thread_deadlock.htm 참조
static class Thread1 extends Thread{

	@Override
	public void run() {
		synchronized (lock1) {
			System.out.println("thread1");
			try {
				Thread.sleep(1000);
			} catch (Exception e) {}
		
		System.out.println("Thread 1 : Waiting for lock 2...");
			synchronized (lock2) {
				System.out.println("thread2");
			}
		}
	}		
}

synchronized (lock1) {} 작업을 통해 lock1의 작업은 동기화가 보장될 수 있게 됩니다. 

 

데드락

데드락(교착상태)

https://ko.wikipedia.org/wiki/%EA%B5%90%EC%B0%A9_%EC%83%81%ED%83%9C

 

교착 상태 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 데드락은 여기로 연결됩니다. 다른 뜻에 대해서는 데드락 (동음이의) 문서를 참조하십시오. 교착 상태(膠着狀態, 영어: deadlock)란 두 개 이상의 작업이 서로 상

ko.wikipedia.org

데드락이 가장 많이 비교되는 예시는 교차로입니다. 

교차로에서 차가 저런식으로 막혀버리면 어떻게 풀어내야 할까요...? 생각만해도 답이 안나옵니다.

이런 답안나오는 상황을 데드락이라고 진짜로 다른 프로세스에 막혀서 서로 답을 못 얻어 프로세스를 끝낼 수 없는 데드락 상태에 빠져버리게 됩니다. 

간단하게

  • A라는 프로세스는 B에서 답을 얻어야 정상적으로 작업을 마치고 프로세스를 종료할 수 있습니다
  • B라는 프로세스는 A에서 답을 얻어야 정상적으로 작업을 마치고 프로세스를 종료할 수 있습니다.

이런 프로세스가 동시에 작동된다면 작업은 어떤식으로 진행이 될까요.?

A 프로세스는 진행하다가 B에게 답을 요구하지만 B는 아직 프로세스 중이기 때문에 답을줄 수 있는 상태가 아닙니다. 역시 B 또한 A에게 답읍 요구하였지만 A도 프로세스가 진행중이기 때문에 답을줄 수 없습니다. 

이렇게 되면 중간에 누군가 개입해서 중재 하지 않으면 영원히 A,B는 서로에게 답을 기다리기만 하는 데드락에 빠지게 됩니다.

// https://www.tutorialspoint.com/java/java_thread_deadlock.htm 참조

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadTest {
	private static Lock lock1 = new ReentrantLock(true);
	private static Lock lock2 = new ReentrantLock(true);
	
	public static void main(String[] args) {
		Thread1 t1 = new Thread1();
		Thread2 t2 = new Thread2();
		t1.start();
		t2.start();
	}
	
	static class Thread1 extends Thread{

		@Override
		public void run() {
			synchronized (lock1) {
				System.out.println("thread1");
				try {
					Thread.sleep(1000);
				} catch (Exception e) {}
			
			System.out.println("Thread 1: Waiting for lock 2...");
				synchronized (lock2) {
					System.out.println("thread2");
				}
			}
		}		
	}
	
	static class Thread2 extends Thread{

		@Override
		public void run() {
			synchronized (lock2) {
				System.out.println("thread2");
				try {
					Thread.sleep(1000);
				} catch (Exception e) {}
			
			System.out.println("Thread 1: Waiting for lock 1...");
			
				synchronized (lock1) {
					System.out.println("thread1");
				}
			}
		}
	}
}
thread1
thread2
Thread 1: Waiting for lock 2...
Thread 2: Waiting for lock 1...

 

예시 코드를 가지고 테스트 했을때 실제로  Thread 1: Waiting for lock 2...  , Thread 2: Waiting for lock 1...  뒤로는 프로그램이 계속 대기상태에 빠지는 걸 확인할 수 있었습니다.

728x90

댓글