티스토리 뷰
스레드(thread)란?
명령문이 순서대로 하나씩 처리되는 것. 즉, 프로그램의 실행 흐름.
멀티스레드 프로그램(multi-thread program)이란?
둘 이상의 실행 흐름을 갖는 프로그램.
멀티스레드 프로그램의 작동 방식
- 메인 스레드만 프로그램이 시작되면 자동으로 시작되고, 다른 스레드들은 메인 스레드에서 만들어서 시작한다.
- 메인 스레드가 끝나더라도 다른 스레드는 끝나지 않고 실행을 계속할 수 있다.
- 스레드는 동시에 실행되는 것이 아니라 자바 가상 머신이 스레드를 번갈아 실행한다.
멀티스레드 프로그램의 작성 방법
1. Thread 클래스를 이용하는 방법
class AlphabetThread extends Thread {
public void run () {
for (char ch = 'A'; ch <= 'Z'; ch++) {
System.out.print(ch);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class DigitThread extends Thread {
public void run() {
for (int cnt = 0; cnt < 10; cnt++) {
System.out.print(cnt);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Main {
public static void main(String[] args) {
Thread thread1 = new DigitThread();
Thread thread2 = new AlphabetThread();
thread1.start();
thread2.start();
}
}
2. Runnable 인터페이스를 이용하는 방법
class KoreanLetters implements Runnable {
public void run(){
char[] arr = {'ㄱ', 'ㄴ', 'ㄷ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅅ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅌ', 'ㅍ', 'ㅎ'};
for(char ch : arr) {
System.out.print(ch);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class SmallLetters implements Runnable {
public void run () {
for (char ch = 'a'; ch <= 'z'; ch++){
System.out.print(ch);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Main {
public static void main(String[] args) {
SmallLetters obj1 = new SmallLetters();
KoreanLetters obj2 = new KoreanLetters();
Thread thread1 = new Thread(obj1);
Thread thread2 = new Thread(obj2);
thread1.start();
thread2.start();
}
}
스레드간의 커뮤니케이션
스레드간의 데이터 교환
공유 영역을 만들어서 여러 스레드들이 데이터를 교환할 수 있다.
class SharedArea {
double result;
volatile boolean isReady;
// 자바 컴파일러는 성능 향상을 위해서 cpu cache에 값을 가져다 놓고 사용하는데,
// 그렇게 되면 최초에 가져온 false가 true로 바뀌지 않아서 while 문이 무한 루프에 빠지게 된다.
// volatile을 선언하여 cpu cache가 아니라 main memory에서 값을 읽어온다.
}
class CalcThread extends Thread {
SharedArea sharedArea;
public void run(){
double total = 0.0;
for(int cnt = 1; cnt < 1000000000; cnt += 2)
if (cnt / 2 % 2 == 0)
total += 1.0 / cnt;
else
total -= 1.0 / cnt;
sharedArea.result = total * 4;
sharedArea.isReady = true; //작업이 끝난 상태를 전달
}
}
class PrintThread extends Thread {
SharedArea sharedArea;
public void run () {
while (sharedArea.isReady != true) {
continue;
}
System.out.println(sharedArea.result);
}
}
class Main {
public static void main(String[] args) {
CalcThread thread1 = new CalcThread();
PrintThread thread2 = new PrintThread();
SharedArea obj = new SharedArea();
thread1.sharedArea = obj;
thread2.sharedArea = obj;
thread1.start();
thread2.start();
}
}
critical section의 동기화
스레드가 적절치 못한 순간에 다른 스레드로 제어가 넘어가는 것을 방지할 수 있다.
class SharedArea {
Account account1;
Account account2;
}
class TransferThread extends Thread {
SharedArea sharedArea;
TransferThread (SharedArea area) {
sharedArea = area;
}
public void run() {
for(int cnt = 0; cnt < 12; cnt++) {
//critical section start
sharedArea.account1.withdraw(1000000);
System.out.print("이몽룡 계좌: 100만원 인출, ");
sharedArea.account2.deposit(1000000);
System.out.println("성춘향 계좌: 100만원 입금");
//critical section end
}
}
}
class PrintThread extends Thread {
SharedArea sharedArea;
PrintThread (SharedArea area) {
sharedArea = area;
}
public void run () {
for(int cnt = 0; cnt < 3; cnt++){
// critical section start
int sum = sharedArea.account1.balance +
sharedArea.account2.balance;
System.out.println("계좌 잔액 합계: " + sum);
// critical section end
try {
Thread.sleep(1);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
}
}
class Account {
String accountNo;
String ownerName;
int balance;
Account (String accountNo, String ownerName, int balance) {
this.accountNo = accountNo;
this.ownerName = ownerName;
this.balance = balance;
}
void deposit (int amount) {
balance += amount;
}
int withdraw (int amount) {
if (balance < amount)
return 0;
balance -= amount;
return amount;
}
}
class Main {
public static void main(String[] args) {
SharedArea area = new SharedArea();
area.account1 = new Account("111-111-1111", "이몽룡", 20000000);
area.account2 = new Account("222-222-2222", "성춘향", 10000000);
TransferThread thread1 = new TransferThread(area);
PrintThread thread2 = new PrintThread(area);
thread1.start();
thread2.start();
}
}
1. 동기화 블록(synchronized block)을 이용한 동기화 방법
synchronize (공유_객체) {
critical section
}
class PrintThread extends Thread {
SharedArea sharedArea;
PrintThread (SharedArea area) {
sharedArea = area;
}
public void run () {
for(int cnt = 0; cnt < 3; cnt++){
// 동기화 블록 시작
synchronized (sharedArea) {
int sum = sharedArea.account1.balance +
sharedArea.account2.balance;
System.out.println("계좌 잔액 합계: " + sum);
}
// 동기화 블록 끝
try {
Thread.sleep(1);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
}
}
class TransferThread extends Thread {
SharedArea sharedArea;
TransferThread (SharedArea area) {
sharedArea = area;
}
public void run(){
for(int cnt = 0; cnt < 12; cnt++) {
// 동기화 블록 시작
synchronized (sharedArea) {
sharedArea.account1.withdraw(1000000);
System.out.print("이몽룡 계좌: 100만원 인출, ");
sharedArea.account2.deposit(1000000);
System.out.println("성춘향 계좌: 100만원 입금");
}
// 동기화 블록 끝
}
}
}
2. 동기화 메소드를 이용한 동기화 방법
class SharedArea {
Account account1;
Account account2;
// 동기화 메소드 시작
synchronized void transfer(int amount) {
account1.withdraw(amount);
System.out.print("이몽룡 계좌: 100만원 인출, ");
account2.deposit(amount);
System.out.println("성춘향 계좌: 100만원 입금");
}
// 동기화 메소드 끝
// 동기화 메소드 시작
synchronized int getTotal() {
return account1.balance + account2.balance;
}
// 동기화 메소드 끝
}
class TransferThread extends Thread {
SharedArea sharedArea;
TransferThread (SharedArea area) {
sharedArea = area;
}
public void run(){
for(int cnt = 0; cnt < 12; cnt++) {
sharedArea.transfer(1000000); // 동기화 메소드 호출
}
}
}
class PrintThread extends Thread {
SharedArea sharedArea;
PrintThread (SharedArea area) {
sharedArea = area;
}
public void run () {
for(int cnt = 0; cnt < 3; cnt++){
int sum = sharedArea.getTotal(); // 동기화 메소드 호출
System.out.println("계좌 잔액 합계: " + sum);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
}
}
스레드간의 신호 전송
스레드간 직접 신호를 주고 받을 수 있다.
메소드 종류
- notify() : 다른 스레드로 신호를 보내는 메소드
- wait() : 다른 스레드로부터 신호가 오기를 기다리는 메소드
- notifyAll() : wait 하고 있는 모든 스레드에게 신호를 보내는 메소드
notify()와 wait()의 사용 방법
class CalcThread extends Thread {
SharedArea sharedArea;
public void run(){
double total = 0.0;
for(int cnt = 1; cnt < 1000000000; cnt += 2)
if (cnt / 2 % 2 == 0)
total += 1.0 / cnt;
else
total -= 1.0 / cnt;
sharedArea.result = total * 4;
sharedArea.isReady = true;
synchronized (sharedArea) {
sharedArea.notify(); // 계산을 완료하면 신호를 보냄
}
}
}
class PrintThread extends Thread {
SharedArea sharedArea;
public void run () {
if (sharedArea.isReady != true) { // 반복적으로 상태를 검사해야 했던 while 문을 제거
try {
synchronized (sharedArea) {
sharedArea.wait(); // 신호를 받음
}
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
System.out.println(sharedArea.result);
}
}
notifyAll()의 사용 방법
class CalcThread extends Thread {
SharedArea sharedArea;
public void run(){
double total = 0.0;
for(int cnt = 1; cnt < 1000000000; cnt += 2)
if (cnt / 2 % 2 == 0)
total += 1.0 / cnt;
else
total -= 1.0 / cnt;
sharedArea.result = total * 4;
sharedArea.isReady = true;
synchronized (sharedArea) {
sharedArea.notifyAll(); // 기다리는 모든 스레드로 신호를 보냄
}
}
}
class PrintThread extends Thread {
SharedArea sharedArea;
public void run () {
if (sharedArea.isReady != true) {
try {
synchronized (sharedArea) {
sharedArea.wait(); // 신호를 기다림
}
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
System.out.println(sharedArea.result);
}
}
class SimplePrintThread extends Thread {
SharedArea sharedArea;
public void run () {
if (sharedArea.isReady != true) {
try {
synchronized (sharedArea) {
sharedArea.wait(); // 신호를 기다림
}
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
System.out.printf("%.2f %n", sharedArea.result);
}
}
class LuxuryPrintThread extends Thread {
SharedArea sharedArea;
public void run () {
if (sharedArea.isReady != true) {
try {
synchronized (sharedArea) {
sharedArea.wait(); // 신호를 기다림
}
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
System.out.println("*** pi = " + sharedArea.result + " ***");
}
}
스레드의 상태
스레드의 라이프 사이클
실행되기 전 상태 New Thread |
→ | 실행 가능 상태 Runnable |
→ | 실행을 끝낸 상태 Dead Thread |
↑↓ | ||||
실행 불가능 상태 Not Runnable |
스레드의 상태를 알아내는 메소드
Thread.State state = thread.getState();
Thread.State 라는 타입은 Thread 클래스 안에 선언되어 있는 State 열거 타입
열거 타입 Thread.State의 열거값
열거 상수 | 의미하는 스레드의 상태 |
NEW | 실행되기 전 상태 |
RUNNABLE | 실행 가능 상태 |
WAITING | wait 메소드를 호출하고 있는 상태 |
TIMED_WAITING | sleep 메소드를 호출하고 있는 상태 |
BLOCKED | 다른 스레드의 동기화 블록이나 동기화 메소드가 끝나기를 기다리고 있는 상태 |
TERMINATED | 실행을 마친 상태 |
라이프 사이클에 해당하는 열거값
- New Thread
- NEW
- Runnable
- RUNNABLE
- Dead Thread
- TERMINATED
- Not Runnable
- WAITING
- TIMED_WAITING
- BLOCKED
사용 방법
class MonitorThread extends Thread {
Thread thread;
MonitorThread (Thread thread) {
this.thread = thread;
}
public void run() {
while (true) {
Thread.State state = thread.getState();
System.out.println("스레드의 상태: " + state);
if (state == Thread.State.TERMINATED) {
break;
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Main {
public static void main(String[] args) {
CalcThread thread1 = new CalcThread();
PrintThread thread2 = new PrintThread();
MonitorThread thread3 = new MonitorThread(thread1); //thread1 모니터링 스레드 생성
SharedArea obj = new SharedArea();
thread1.sharedArea = obj;
thread2.sharedArea = obj;
thread1.start();
thread2.start();
thread3.start(); // 모니터링 스레드 시작
}
}
참고
<뇌를 자극하는 Java 프로그래밍>, 한빛미디어
'Language > Java' 카테고리의 다른 글
Exception 클래스 (0) | 2022.10.18 |
---|---|
우아한테크캠프 Pro 5기 프리코스 - JUnit, AssertJ 학습하기 (2) | 2022.10.04 |
자바 자료구조 (0) | 2022.07.04 |
Overflow가 발생하면 어떻게 될까? (0) | 2022.04.27 |
JVM의 Garbage Collector (0) | 2021.04.09 |
댓글