쓰레드란?
프로세스 내 하나의 실행 흐름 단위이다.
각 프로세스는 최소 1개 이상의 쓰레드를 가진다. (만약 그 쓰레드가 1개인 경우, 그 쓰레드를 메인 쓰레드라 한다)
멀티 스레드를 잘 구성한다면, 멀티 프로세스로 구성할 때에 비해 메모리 공유량이 줄어들어 시스템 자원 소모가 줄어든다. Context Switching 에 대한 오버헤드도 줄어든다.
하지만 프로그래머가 골치를 썩일 문제 또한 많은데 그런 예시 중 대표적인게 "은행 계좌 문제", "식사하는 철학자 문제" 이다. 이중 은행계좌 문제로 알 수 있는 건, Critical Section 문제다. 임계구역 문제인데 같은 자원을 서로 다른 쓰레드가 점유하고자 해서 생기는 문제점이다. 이 임계구역 문제를 해결하기 위해선 적절한 실행 흐름 제어가 필요하다.
먼저 이게 어째서 문제가 될 수 있나 확인.
Main.java
class Main {
public static void main(String[] args) throws InterruptedException {
BankAccount b = new
BankAccount();
Parent p = new Parent(b);
Child c = new Child(b);
p.start();
c.start();
p.join();
c.join();
System.out.println( "\nbalance = " + b.getBalance());
}
}
BankAccount.java
class BankAccount {
int balance=5000;
void deposit(int amount) {
balance = balance + amount;
}
void withdraw(int amount) {
balance = balance - amount;
}
int getBalance() {
return balance;
}
}
Parent.java
class Parent extends Thread {
BankAccount b;
Parent(BankAccount b) {
this.b = b;
}
public void run() {
for (int i=0; i<100; i++){
try {
Thread.sleep((int)(Math.random()*10));
} catch(InterruptedException e) { }
b.deposit(1000);
System.out.println("천원 저금/ 남은 금액:"+b.getBalance());
}
}
}
Child.java
class Child extends Thread {
BankAccount b;
Child(BankAccount b) {
this.b = b;
}
public void run() {
for (int i=0; i<100; i++)
{
try {
Thread.sleep((int)(Math.random()*10));
} catch(InterruptedException e) { }
b.withdraw(1000);
System.out.println("천원 인출/ 남은 금액:"+b.getBalance());
}
}
}
이 4개의 파일을 통해, Parent 는 쓰레드 실행 시 천원씩을 100번 저금하고, Child 는 천원을 100번 인출 한다.
두 개의 쓰레드는 balance라는, 같은 계좌를 접근한다.
자바의 쓰레드 실행 방법
1. Runnable interface 를 구현하거나, Thread 클래스를 상속해 run() method 를 오버라이딩해 적절한 실행 내용을 구현하는 2가지 방법이 있는데 후자를 택했다.
2. run() method 구현을 하고, 쓰레드를 생성하고자 하는 코드부에서 class instance 를 생성한 뒤 start() 메소드를 호출한다. 그러면 메소드가 run() 의 실행을 위해 대기하고 차례가 오면 실행한다.
예상되는 결과는 무엇일까? 선언된 순서대로 쓰레드가 각각 저금과 이체를 다 실행한다면 정상적으로 남은 계좌금액은 원래있던 잔액 5000원이 되어야 한다. Thread.join() method 덕분에, 각 쓰레드가 끝난 이후에야 잔액이 print 되기 때문에 인출과 저금의 순서에 상관없이 마지막 출력 결과는 동일해야한다.
하지만 결과는?
실행 결과
thread.sleep 에 의해, 인출(혹은 저금)의 과정이 끝나기전에 다음 저금(혹은 인출) 이 발생해 어느 순간부터 계좌의 금액은 어그러지게 된다. 당연히 이런 식이면 은행은 쫄딱 망할 것이다.
이런 임계구역에서의 동기화 문제를 해결하기 위해 등장한 것이 semaphore,Monitor,Mutex 등이다.
원리를 알아보는게 중요하지만, 자바코드로 이 동기화를 보장하는 방법만 테스트해보겠다.
class BankAccount {
int balance=5000;
synchronized void deposit(int amount) {
balance = balance + amount;
}
synchronized void withdraw(int amount) {
balance = balance - amount;
}
int getBalance() {
return balance;
}
}
BankAccount.java 의 임계구역의 상호배타성,원자성이 보장되어야 하는 메소드들에 대해 다음과 같이 'synchronized' 를 추가한다.
출처:
www.kocw.or.kr/home/cview.do?mty=p&kemId=978503&ar=pop
'프로그래밍 > JAVA' 카테고리의 다른 글
[Java] == 와 equals() 의 차이 (0) | 2020.10.24 |
---|---|
[Java] abstract class 추상클래스란? (0) | 2020.10.22 |
[Java] StringTokenizer, StringBuffer, StringBuilder 클래스 차이 (0) | 2020.10.03 |
[JAVA] 예외 (Exception) (0) | 2020.09.29 |
[JAVA] 다형성 (상속, 오버라이딩, 업캐스팅) (0) | 2020.09.19 |