티스토리 뷰
class Bank{
private int money = 10000;
public void saveMoney(int save) {
int m = this.getMoney();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
setMoney(m+save);
}
public void minusMoney(int minus) {
int m = this.getMoney();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
setMoney(m - minus);
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
}
class Park extends Thread {
@Override
public void run() {
System.out.println("saveThread 실행!");
SyncMain.myBank.saveMoney(3000);
System.out.println("남은 돈 : " + SyncMain.myBank.getMoney());
}
}
class Kim extends Thread {
@Override
public void run() {
System.out.println("minusThread 실행!");
SyncMain.myBank.minusMoney(1000);
System.out.println("남은 돈 : " + SyncMain.myBank.getMoney());
}
}
public class SyncMain {
public static Bank myBank = new Bank(); // 객체 자체를 static 으로 생성
public static void main(String[] args) {
Park park = new Park();
park.start();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Kim kim = new Kim();
kim.start();
}
}
=======================================출력==========================================
saveThread 실행!
minusThread 실행!
남은 돈 : 9000
남은 돈 : 13000
위의 코드를 살펴보면 Bank 클래스를 생성한 후 해당 클래스의 메소드로 save 와 minus를 생성한다.
각각의 메소드에서 스레드 sleep을 설정하여 스레드가 실행되는데 시간의 갭을 둔다.
SyncMain 함수에서 Bank 클래스의 객체를 생성한다. 해당 객체는 static으로 전역 객체로 생성한다.
그 이유는 Park과 Kim 의 클래스에서 해당 객체의 메소드를 사용하기 위해서이다.
Park과 Kim 클래스는 Thread 클래스를 extends 하였다.
상속받은 클래스는 run() 메소드를 오버라이딩 한다.
스레드 실행 시 만들어놓은 static 객체인 myBank 를 사용한다. ==>
Park ==> SyncMain.myBank.saveMoney(값);
Kim ==> SyncMain.myBank.minusMoney(값);
(static 으로 만들어놓은 변수, 객체는 클래스명.객체명.메소드명 으로 사용하면된다.)
결과 보기!!
출력된 내용을 보면 park의 스레드가 먼저 실행되어 saveMoney -> minusMoney 메소드가 차례로 실행된다.
하지만, 결과는 남은 돈 = 9000 -> 남은 돈 : 13000이 출력된다.
만약 kim의 결과가 9000 이라면 후에 3000원이 save되어 12000원이 되어야 하는데 13000이 된걸 볼 수 있다.
이러한 결과가 나타나는 이유를 알아보자
위는 스레드가 실행되는 상황을 보여준다.
두 스레드는 하나의 resource은 m을 공유하고 있다.
park의 스레드가 실행 된 후 3초의 sleep이 일어나는 중간에 kim의 스레드가 실행이 되어 각각 하나의 resource인 m을 동시에 사용하는 경우가 발생하게 된다.
park이 실행되는 도중 kim이 실행되어 minus를 일으키고 그 값인 9000이 출력 된다.
하지만 park은 sleep인 상태로 변경된 m = 9000의 값을 getMoney() 하지 못하고 맨 처음 실행하면서 갖고 있던 resource인 m = 10000을 갖고 save 3000 을 하게 된다.
따라서 결과는 남은 돈 = 9000 -> 남은 돈 : 13000이 된다.
이를 통해서 하나의 resource를 사용하는 경우 우리는 동기화를 통해서 하나의 스레드가 resource를 사용하는 경우 lock을 걸어 다른 스레드가 사용하지 못하게 해야한다.
그렇다면 동기화는 어떤 작업을 하는가?
- 두 개의 thread 가 같은 객체에 접근 할 경우, 동시에 접근 함으로써 오류가 발생
- 동기화는 임계영역에 접근한 경우 공유자원을 lock 하여 다른 thread의 접근을 제어
- 동기화를 잘못 구현하면 deadlock에 빠질 수 있다.
## 동기화(synchronized) 를 통해 올바른 코드를 작성해보자
saveMoney, minusMoney 메소드를 synchronized 하여 실행을 한다.
public synchronized void saveMoney(int save) {
int m = this.getMoney();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
setMoney(m+save);
}
public synchronized void minusMoney(int minus) {
int m = this.getMoney();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
setMoney(m - minus);
}
=======================================출력============================
saveThread 실행!
minusThread 실행!
남은 돈 : 13000
남은 돈 : 12000
메소드에 synchronized를 설정하여 동기화 한 코드이다.
출력을 보면 두 스레드가 실행된 후 saveMoney 메소드가 실행 된 후의 결과인 13000이 출력되고 후에 12000의 결과가 출력된다.
# sync block 사용하기
동기화를 적용하기 위해서 위에서는 메소드에 synchronized를 적어 사용하였다.
하지만 sync block을 사용하여서 적용할 수 도 있다.
class Bank{
private int money = 10000;
public void saveMoney(int save) {
int m = this.getMoney();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
setMoney(m+save);
}
public void minusMoney(int minus) {
int m = this.getMoney();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
setMoney(m - minus);
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
}
class Park extends Thread {
@Override
public void run() {
synchronized (SyncMain.myBank) {
// sync block 을 사용하는 경우 괄호에 lock을 걸어야 하는 shared resource를 넣어주면 된다.
//어떤 resource 에다 sync를 걸건지 명확하게 적어주면 된다.
System.out.println("saveThread 실행!");
SyncMain.myBank.saveMoney(3000);
System.out.println("남은 돈 : " + SyncMain.myBank.getMoney());
}
}
}
class Kim extends Thread {
@Override
public void run() {
synchronized (SyncMain.myBank) {
System.out.println("minusThread 실행!");
SyncMain.myBank.minusMoney(1000);
System.out.println("남은 돈 : " + SyncMain.myBank.getMoney());
}
}
}
public class SyncMain {
public static Bank myBank = new Bank(); // 객체 자체를 static 으로 생성
public static void main(String[] args) {
Park park = new Park();
park.start();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Kim kim = new Kim();
kim.start();
}
}
====================================출력=================================
saveThread 실행!
남은 돈 : 13000
minusThread 실행!
남은 돈 : 12000
출력에서 찍혀진 로그를 살펴보면 위에 다르다는 점을 볼 수 있다.
saveThread가 실행된 후 결과를 내고, 값을 로그로 찍은 후 minucThread가 실행되는 것을 볼 수 있다.
출력이 다르게 나타난 이유는 로그를 찍는 것 또한 sync block 안에 있기 때문에 동기화로 인하여 해당 스레드가 실행되는 동안 lock을 하여 다른 스레드가 실행되지 않고 실행 중인 스레드의 수행이 완료된 후 다음 스레드가 실행되게 된다.
'개인공부 > JAVA' 카테고리의 다른 글
Exception - Runnable(학교공부) (0) | 2021.11.02 |
---|---|
Exception - throws (학교공부) (0) | 2021.09.17 |
Exception (0) | 2021.09.09 |
배열, 기본클래스 (0) | 2021.05.05 |
JAVA - Inner Class, Lambda , Stream (0) | 2021.03.21 |