자바의 멀티쓰레드 프로그래밍에 대해 학습하기
: 실행 중인 프로그램
프로그램(크롬)을 여러개 실행하면 각각 프로세스가 된다.
구성
대부분의 OS(윈도우, 유닉스 등)는 멀티태스킹을 지원하기 때문에 여러 개의 프로세스가 동시에 실행될 수 있다.
: 프로세스라는 작업 공간에서 작업을 처리하는 일꾼
멀티 쓰레딩의 핵심은 동시에 작업을 수행하는 것이다.
따라서 멀티쓰레드 프로그램은 메신저로 채팅하면서 파일을 다운로드 받거나 음성대화를 나눌 수 있다.
쓰레드를 구현하는 방법은 두가지가 존재한다.
Thread
클래스는 Runnable
인터페이스를 구현하였다.
Thread
클래스를 상속받은 클래스를 생성하여 run()
을 오버라이딩한다.
Thread
클래스
public class Thread implements Runnable {
private Runnable r;
public Thread(Runnable r) {
this.r = r;
}
@Override
public void run() {
if(r! = null) {
r.run();
}
}
}
Thread
클래스를 상속받은 클래스 생성
class MyThread extends Thread {
@Override
public void run() { // 실행할 작업을 적는다.
for (int i = 0; i < 5; i++) {
// 스레드의 이름을 출력한다.
// 부모인 Thread 클래스의 getName()를 호출한다.
System.out.println(getName());
}
}
}
생성한 클래스의 인스턴스 실행
class Main {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start(); // 쓰레드를 실행시킨다.
}
}
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Runnable
인터페이스를 구현한 클래스의 인스턴스를 생성한 다음, 이 인스턴스를 Thread
클래스의 생성자의 매개변수로 제공하여 Thread
클래스의 인스턴스를 생성한다.
Thread
클래스에서 상속을 통해, run()
을 오버라이딩하지 않고도 외부로 run()
을 제공받는다.
Thread
클래스
public class Thread implements Runnable {
private Runnable r;
public Thread(Runnable r) {
this.r = r;
}
@Override
public void run() {
if(r! = null) {
r.run();
}
}
}
Runnable
인터페이스를 구현한 클래스
class MyThread implements Runnable {
@Override
public void run() { // 실행할 작업을 적는다.
for (int i = 0; i < 5; i++) {
// 스레드의 이름을 출력한다.
// Thread를 상속받지 않아 Thread의 static 메소드인 Thread.currentThread()로 현재 실행중인 Thread의 참조를 얻어와 getName()을 호출한다.
System.out.println(Thread.currentThread().getName());
}
}
}
Thread
클래스의 생성자로 Runnable
인터페이스를 구현한 클래스를 인자로 전달하여 생성된 Thread
클래스의 인스턴스 실행
class Main {
public static void main(String[] args) {
Runnable r = new MyThread();
Thread myThread = new Thread(r); // 생성자 Thread(Runnable target)
myThread.start(); // 쓰레드를 실행시킨다.
}
}
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
첫번째 방법처럼 Thread
클래스를 상속받으면 다른 클래스를 상속받을 수 없다.
따라서 다른 클래스를 확장할 필요가 있을 때에는 Runnable
인터페이스를 구현하고, 그렇지 않은 경우에는 Thread
클래스를 사용하는 것이 편하다.
방법 : start()
를 호출한다.
쓰레드가 실행되는 과정
main 메소드에서 start()
를 호출한다.
쓰레드가 작업을 실행하는데 필요한 호출 스택(call stack)을 생성한다.
run()
을 호출하여 생성된 호출 스택에 run()
이 첫 번째로 올라가게 한다.
이때 두 쓰레드를 동시에 실행할만큼의 자원이 없다면, 바로 run()
이 실행되지 않고 호출 스택이 2개(Main 쓰레드의 호출스택, 새로운 쓰레드의 호출스택)이므로 스케줄러가 정한 순서에 따라서 번갈아 가면서 실행된다.
작업을 마친 쓰레드(run()
수행이 종료된 쓰레드)는 호출스택이 모두 비워지면서 해당 쓰레드가 사용하던 호출스택이 사라진다.
쓰레드가 두 개 이상 실행중일 때, 두 쓰레드는 자원이 부족하지 않다면 동시에 수행되므로 다음의 결과가 발생할 수 있다.
class MyThread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
// 스레드의 이름을 출력한다.
System.out.println(getName());
}
}
}
class MyThread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
// 스레드의 이름을 출력한다.
System.out.println(getName());
}
}
}
class Main {
public static void main(String[] args) {
MyThread1 myThread1 = new MyThread1();
MyThread2 myThread2 = new MyThread2();
myThread1.start();
myThread2.start();
}
}
Thread-0
Thread-0
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-0
Thread-0
Thread-0
한 번 실행이 종료된 쓰레드는 다시 실행할 수 없다.
start()
를 두 번 이상 호출하면, 실행시에 IllegalThreadStateException
이 발생한다.출처: 멀티쓰레드 프로그래밍
상태 | 의미 |
---|---|
NEW | 쓰레드 객체는 생성되었지만, 아직 시작되지 않은(start() 가 호출되지 않은) 상태 |
RUNNABLE | 실행중인 상태 |
WAITING | 대기중인 상태 |
TIMED_WAITING | 특정 시간만큼 대기중인 상태 |
BLOCKED | 동기화 작업으로 인해 다른 쓰레드 실행이 끝날 때까지 실행 중지 상태이며, 모니터 락이 풀리기를 기다리는 상태 |
TERMINATED | 종료된 상태 |
쓰레드가 둘 이상일 때 스케줄러는 실행대기중인 쓰레드들의 우선순위를 고려하여 실행순서와 실행시간을 결정한다.
: main 메서드의 작업을 수행하는 쓰레드이다.
💡 자바 프로그램이 종료되는 시점
기존에는 main 메소드가 종료되면 자바 프로그램이 종료된다고 알고 있었을 것이다.
하지만, 실제로는 main 메소드가 종료되더라도 즉, Main 쓰레드가 종료되더라도 다른 실행중인 쓰레드가 남아있다고 한다면 해당 쓰레드가 종료될 때까지 자바 프로그램은 종료되지 않는다.
여러개의 쓰레드가 한 개의 리소스를 사용할 때, 다른 쓰레드의 접근을 막는 것
자바에서는 synchronized
키워드를 사용하여 메소드나 블록단위로 동기화를 수행한다.
// 메소드 단위로 동기화 수행
publc synchronized void foo() {...}
// 블록 단위로 동기화 수행
public void foo() {
synchronized(obj) {
...
}
}
: 두 쓰레드가 자원을 점유한 상태에서 서로 상대편이 점유한 자원을 사용하려고 기다리느라 진행이 멈춰있는 상태
Reference
- 자바의 정석 3rd Edition, 남궁성 지음
- 10주차 과제: 멀티쓰레드 프로그래밍