Today's special moments become memories of tomorrow.

JAVA

[java] 스레드(thread)

lotus lee 2021. 4. 15. 00:02

스레드(thread)란, 프로세스 내에서 실행되는 하나의 실행 흐름 단위를 말한다. 

멀티 스레딩은 프로세스 내에 여러 스레드가 동시에 작업을 수행하는 것을 의미한다.

 

자바에서 스레드는 자바 가상 머신(JVM)에 의해 스케줄링 및 관리된다.

스레드의 생명주기를 비롯해 스레드 개수, 스레드 간의 우선순위 등을 JVM이 관리한다.

자바에서 개발자가 스레드를 생성하는 코드를 작성하면, JVM은 스레드를 생성한다.

 

스레드 구현

자바에는 스레드를 구현하는 두 가지 방법이 있다.

 

1. java.lang.Thread 클래스 상속

2. java.lang.Runnable 인터페이스 구현

 

 

1. Thread 클래스 상속

Thread가 클래스이므로 extends 키워드를 이용하여 상속한다. Thread 클래스를 상속하면 run() 메서드를

오버라이딩 해야하며, 스레드를 통해 하고 싶은 작업을 이 run() 메서드 안에 작성해야 한다.

스레드가 시작되면 run() 메서드 안에 작성된 코드가 실행된다. 

 

0부터 9까지의 수를 1초마다 한 번씩 출력하는 MyThread 스레드를 작성하는 코드다.

static class MyThread extends Thread {

	@Override
	public void run() {

		for (int i = 0; i < 10; i++) {
			System.out.println(i);

			try {
				sleep(1000);
			} catch (InterruptedException e) {
				return;
			}
		}
	}
}

Thread 클래스를 상속하여 만든 MyThread 스레드를 실행시키려면, MyThread 객체를 생성한 후 start() 메서드를 호출한다.

public static void main(String[] args) throws Exception {

	MyThread myThread = new MyThread();
	myThread.start();
}

 

MyThread 실행 결과 : 

2. Runnable 인터페이스 구현

implements 키워드를 이용하여 Runnable 인터페이스를 구현한다.

마찬가지로 run() 메서드를 오버라이딩하고, 스레드가 할 작업을 이 메서드 안에 작성해야 한다.

static class MyThread implements Runnable {

	@Override
	public void run() {

		for (int i = 0; i < 10; i++) {
			System.out.println(i);

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

 

단, Runnable 인터페이스를 구현하는 경우에는 스레드 객체를 생성한 뒤에 이 객체를 Thread() 생성자의

인자로 넘겨주는 추가적인 작업이 필요하다. 

public static void main(String[] args) throws Exception {

	MyThread myThread = new MyThread();
	Thread th = new Thread(myThread); // Thread() 생성자의 인자로 넘겨줌

	th.start();
}

 

 

스레드 상태

스레드는 크게 6가지 상태로 나눌 수 있다.

 

NEW : 스레드가 생성되었지만, 아직 실행하지 않은 상태

 

RUNNABLE : 스레드가 JVM에 의해 실행되거나 실행 준비 상태

 

WAITING : 일시정지 상태. 다른 스레드가 notify() 혹은 notifyAll()를 호출하면 깨어남

 

TIME_WAITING : sleep(n) 호출로 인해 n밀리초 동안 일시정지 상태

 

BLOCK : 동기화를 위해 lock이 걸린 상태. 동기화 블럭에 진입하려고 할 때, 다른 스레드가 이미 진입하면

                   BLOCK 상태가 되어 진입할 차례를 기다린다.

 

TERMINATED : 스레드 종료

 

 

스레드 동기화

멀티 스레딩 환경에 스레드 동기화는 매우 중요한 개념이다.

여러 스레드가 공유 자원에 동시에 접근하고자 할 때, 동기화가 되어있지 않으면 원하는 결과를 얻을 수 없기 때문이다.

 

예제를 하나 살펴보자.

변수 value와, 이 value변수의 값을 10 증가시키는 add() 메서드로 구성된 Counter 클래스가 있다.

static class Counter {

	private int value = 0;

	public void add() {

		value += 10;
		System.out.println(Thread.currentThread().getName() + " : " + value);
	}
}

스레드 2개를 생성하고, 이 두 개의 스레드는 Counter 객체 내의 add() 메서드를 호출하여 value 변수에 접근한다. 각각의 스레드는 add() 함수를 10번 호출하는 작업을 한다. 그러면 각 스레드는 기존의 value값에 100을 더하게 되는 것이다. 총 두 개의 스레드가 이 작업을 수행하므로, 스레드의 실행이 끝나고 나면 value값은 처음보다 200이 증가해야 한다.

 

아래 코드는 스레드를 상속한 MyThread 클래스를 정의한 것이다.

Counter 객체의 add() 메서드를 호출하기 위해 MyThread() 생성자의 인자로 Counter 객체를 받는다.

static class MyThread extends Thread {

	private Counter counter;

	MyThread(String name, Counter counter) {
		super(name);
		this.counter = counter;
	}

	@Override
	public void run() {

		for (int i = 0; i < 10; i++) {
			counter.add();
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

main() 에서 두 개의 스레드 객체를 생성하여 실행시킨다.

예상대로라면 스레드 실행이 끝나고나면 value가 200이 되어야 하지만, 막상 실행 결과를 출력해보면

그렇지 않음을 확인할 수 있다.

public static void main(String[] args) throws Exception {

	Counter counter = new Counter();

	MyThread th1 = new MyThread("A", counter);
	MyThread th2 = new MyThread("B", counter);

	th1.start();
	th2.start();

}

 

실행 결과 : 

실행이 끝나고 나면, 마지막으로 확인한 value값은 200이 아닌 160이 된다.

 

 

 

이러한 결과가 나오는 이유는 두 스레드 간에 동기화가 이루어지지 않았기 때문이다.

 

처음에 A 스레드가 value = 0 에서 value+=10을 수행하여 value가 10이 되었다고 하자.

그런데 B 스레드도 마찬가지로 value = 0 일 때, value에 접근하여 value +=10을 수행한다.

그러면 A 스레드도 10을 증가시키고, B 스레드도 10을 증가시켰으니 결과적으로 value가 20이 되어야 하지만, 두 스레드가 value = 0 일 때 동시에 접근하는 바람에 총 value값은 10 밖에 증가하지 않는 것이다.

 

 

그래서 자바에서는 synchronized 라는 키워드를 사용하여 한번에 하나의 스레드만 접근하도록 한다.

synchronized 부분이 실행되면, 해당 영역에 진입한 스레드가 모니터를 소유하게 되며, 

이 스레드가 모니터를 내려놓을 때까지 다른 스레드는 이 영역에 진입할 수 없다.

 

방금 전 예제에서 공유 변수인 value에 접근하는 부분은 add() 메서드였다.

따라서, add() 메서드 앞에 synchronized 키워드를 추가하여 해당 영역에서 동기화가 가능하도록 변경하자.

static class Counter {

	private int value = 0;

	public synchronized void add() {

		value += 10;
		System.out.println(Thread.currentThread().getName() + " : " + value);
	}
}

 

그리고 다시 실행하면 이번에는 실행 결과가 예상대로 value가 200이 출력되는 것을 확인할 수 있다.

 

 

wait(), notify(), notifyAll()

멀티 스레딩 환경에서 synchronized 키워드 외에도 동기화를 위해 제공되는 메서드들이 있다.

이 세 가지 메서드는 synchronized 영역, 즉 동기화가 가능한 영역에서만 사용이 가능하다.

또한 이 메서드들은 Object의 메서드이다. 

 

wait() : 실행 중인 스레드를 잠시 WAITING 상태로 만드는 메서드

                 wait()을 호출하면, 다른 스레드가 notify()나 notifyA()을 통해 깨워줄 때까지 기다려야 한다.

 

notify() : wait()로 인해 WAITING 상태인 스레드를 깨워서 RUNNABLE 상태로 변경해준다.

                   여러 개의 스레드가 WAITING 상태여도, 단 한 개의 스레드만을 깨울 수 있다.

 

notifyAll() : notify()와 동일한 역할을 한다. 다만, notify()가 한 개의 스레드만을 깨울 수 있다면,

                       notifyAll()는 WAITING 상태인 모든 스레드를 깨울 수 있다.

 

 

 

'JAVA' 카테고리의 다른 글

[java] HashMap, TreeMap, LinkedHashMap  (0) 2021.04.14
[java] 예외 처리  (0) 2021.04.03
[java] String, StringBuffer, StringBuilder  (0) 2021.04.02
[java] ArrayList, LinkedList, Vector  (0) 2021.04.01