Language/JAVA

[JAVA] 멀티 스레드

IT수정 2024. 10. 17. 09:53

멀티 스레드

운영체제는 실행 중인 프로그램을 프로세스로 관리한다. 멀티 태스킹은 두 가지 이상의 작업을 동시에 처리하는 것을 말하는데, 이때 운영체제는 멀티 프로세스를 생성해서 처리한다. 하지만 멀티 태스킹이 꼭 멀티 프로세스를 의미하지는 않는다. 하나의 프로세스 내에서 멀티 태스킹을 할 수 있도록 만들어진 프로그램도 있다. 예를 들어 메신저는 채팅 작업을 하면서 동시에 파일 전송 작업을 수행하기도 한다.

 

하나의 프로세스가 두 가지 이상의 작업을 처리할 수 있는 이유는 멀티 스레드가 있기 때문이다. 스레드는 코드의 실행 흐름을 말하는데, 프로세스 내에 스레드가 두 개라면 두 개의 코드 실행 흐름이 생긴다는 의미이다. 멀티 프로세스가 프로그램 단위의 멀티 태스킹이라면 멀티 스레드는 프로그램 내부에서의 멀티 태스킹이라고 볼 수 있다.

 

멀티 프로세스들은 서로 독립적이므로 하나의 프로세스에서 오류가 발생해도 다른 프로세스에게 영향을 미치지 않는다. 하지만 멀티 스레드는 프로세스 내부에서 생성되기 때문에 하나의 스레드가 예외를 발생시키면 프로세스가 종료되므로 다른 스레드에게 영향을 미친다. 예를 들어 워드와 엑셀을 동시에 사용하다가 워드에 오류가 생겨도 엑셀은 여전히 사용가능하다. 하지만 메신저에서 파일을 전송하다가 오류가 생기면 메신저 자체가 종료되기 때문에 채팅도 사용하지 못하게 된다. 그렇기 때문에 멀티 스레드를 사용할 경우에는 예외 처리에 만전을 기해야 한다.

 

멀티 스레드는 데이터를 분할해서 병렬로 처리하는 곳에서 사용하기도 하고, 안드로이드 앱에서 네트워크 통신을 하기 위해 사용하기도 한다. 또한 다수의 클라이언트 요청을 처리하는 서버를 개발할 때에도 사용된다. 프로그램 개발에 있어서 멀티 스레드는 꼭 필요한 기능이기 때문에 반드시 이해하고 활용할 수 있도록 한다.

 

메인 스레드

모든 자바 프로그램은 메인 스레드가 main() 메서드를 실행하면서 시작된다. 메인 스레드는 main() 메서드의 첫 코드부터 순차적으로 실행하다가 마지막 코드를 실행하거나 return 문을 만나면 실행을 종료한다. 메인 스레드는 필요에 따라 추가 작업 스레드들을 만들어서 실행시킬 수 있다. 일반적인 싱글 스레드에서는 메인 스레드가 종료되면 프로세스도 종료된다. 하지만 멀티 스레드에서는 메인 스레드가 다른 스레드보다 먼저 종료되어도 다른 스레드가 계속 실행 중이라면 프로세스는 종료되지 않는다.

 

작업 스레드 생성과 실행

멀티 스레드로 실행하는 프로그램을 개발하려면 먼저 몇 개의 작업을 병렬로 실행할지 결정하고 각 작업별로 스레드를 생성해야 한다. 자바 프로그램은 메인 스레드가 반드시 존재하기 때문에 메인 작업 이외에 추가적인 작업 수만큼 스레드를 생성하면 된다. 자바는 작업 스레드도 객체로 관리하므로 클래스가 필요하다. Thread 클래스로 직접 객체를 생성해도 되지만, 하위 클래스를 만들어 생성할 수도 있다.

 

Thread 클래스로 직접 생성

java.lang 패키지에 있는 Thread 클래스로부터 작업 스레드 객체를 직접 생성하려면 다음과 같이 Runnable 구현 객체를 매개값으로 갖는 생성자를 호출하면 된다.

Thread thread = new Thread(Runnable target);

 

Runnable은 스레드가 작업을 실행할 때 사용하는 인터페이스이다. Runnable에는 run() 메서드가 정의되어 있는데, 구현 클래스는 run() 메서드를 재정의해서 스레드가 실행할 코드를 가지고 있어야 한다.

class Task implements Runnable {
	@Override
    public void run() {
    	//스레드가 실행할 코드
        }
   }

 

Runnable 구현 클래스는 작업 내용을 정의한 것이므로, 스레드에게 전달해야 한다. Runnable 구현 객체를 생성한 후 Thread 생성자 매개값으로 Runnable 객체를 다음과 같이 전달하면 된다.

Runnable task = new Task();

Thread thread = new Thread(task);

 

명시적인 Runnable 구현 객체를 작성하지 않고 Thread 생성자를 호출할 때는 익명구현 객체를 매개값으로 사용할 수 있다. 오히려 이 방법이 더 많이 사용된다.

Thread thread = new Thread(new Runnable() {
	@Override
    public void run() {
    	//스레드가 실행할 코드
        }
   });

 

작업 스레드 객체가 생성되었다고 해서 바로 작업 스레드가 실행되지는 않는다. 작업 스레드를 실행하려면 스레드 객체의 start() 메서드를 호출해야 한다.

thread.start();

 

start() 메서드가 호출되면, 작업 스레드는 매개값으로 받은 Runnable의 run() 메서드를 실행하면서 작업을 처리한다.

 

다음은 메인 스레드가 동시에 두 가지 작업을 처리할 수 없음을 보여주는 예제이다. 원래 목적은 0.5초 주기로 비프음을 발생시키면서 동시에 프린팅까지 하는 작업이었지만, 메인 스레드는 비프음을 모두 발생시킨 다음에야 프린팅을 시작한다.

package ch14;

import java.awt.Toolkit;

public class BeepPrintExample {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Toolkit toolkit = Toolkit.getDefaultToolkit();
		for(int i=0; i<5; i++) {
			toolkit.beep();
			try { Thread.sleep(500); } catch(Exception e) {}
		}
		
		for(int i=0; i<5; i++) {
			System.out.println("띵");
			try { Thread.sleep(500); } catch(Exception e) {}
		}
	}

}

 

원래 목적으로 0.5초 주기로 비프음을 발생시키면서 동시에 프린팅을 하고 싶다면 두 작업 중 하나를 작업 스레드에서 처리하도록 해야 한다. 다음과 같이 수정해 보자.

package ch14;

import java.awt.Toolkit;

public class BeepPrintExample {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Thread thread = new Thread(new Runnable() {
			@Override
			public void run() {
				Toolkit toolkit = Toolkit.getDefaultToolkit();
				for(int i=0; i<5; i++) {
					toolkit.beep();
					try { Thread.sleep(500); } catch(Exception e) {}
				}
			}
		});
		
		thread.start();
		
		for(int i=0; i<5; i++) {
			System.out.println("띵");
			try { Thread.sleep(500); } catch(Exception e) {}
		}
	}

}

 

Thread 자식 클래스로 생성

작업 스레드 객체를 생성하는 또 다른 방법은 Thread의 자식 객체로 만드는 것이다. Thread 클래스를 상속한다음 run() 메서드를 재정의해서 스레드가 실행할 코드를 작성하고 객체를 생성하면 된다.

 

다음은 이전 예제를 수정하여 Thread의 익명 자식 객체로 작업 스레드를 정의한 것이다.

package ch14;

import java.awt.Toolkit;

public class BeepPrintExample {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Thread thread = new Thread() {
			@Override
			public void run() {
				Toolkit toolkit = Toolkit.getDefaultToolkit();
				for(int i=0; i<5; i++) {
					toolkit.beep();
					try { Thread.sleep(500); } catch(Exception e) {}
				}
			}
		};
		
		thread.start();
		
		for(int i=0; i<5; i++) {
			System.out.println("띵");
			try { Thread.sleep(500); } catch(Exception e) {}
		}
	}

}

 

'Language > JAVA' 카테고리의 다른 글

[JAVA] 데몬 스레드와 스레드풀  (3) 2024.10.17
[JAVA] 스레드 메서드  (0) 2024.10.17
[JAVA] 타입 파라미터  (0) 2024.10.16
[JAVA] 제네릭 메서드  (0) 2024.10.16
[JAVA] 제네릭 타입  (0) 2024.10.16