티스토리 뷰

Skill/Programming - Network

7. 스레드 동기화

gyulee0220 2019. 5. 19. 13:58

  같은 프로그램 안에서 작동하는 스레드의 경우 대부분 서로간에 리소스를 공유하게 된다. 만약 스레드가 동시에 공유 리소스에 접근해 값을 변경하게 되면 프로그램에는 치명적인 오류가 발생한다. 스레드 동기화는 각 스레드가 공유 영역을 바라보고 있을 떄, 어떤 스레드를 먼저 접근 시키고 공유 영역의 값을 두개의 스레드가 동시에 접근하지 못하도록 막는 기능을 수행한다.


1. 스레드 동기화(Thread Synchronization)


  스레드 동기화는 공유 리소스를 가지고 있는 프로그램에서 스레드 간의 접근을 정의하는 것을 의미한다. 스레드를 동기화 시키기 위해 다양한 방법론이 존재한다. 하나의 스레드가 작업을 끝낼 때 까지 공유 리소스에게 독점적인 권한을 주는 방법, 일정한 시간 간격을 두고 스레드의 공유 리소스 배정을 변경 하는 방법 등 다양한 벙법이 있다.


  아래 5개는 대표적인 스레드 동기화를 의미한다.


 동기화 

 설명

 임계 영역 (critical section)

  공유 리소스에 단 하나의 스레드만 접근 (하나의 프로세스에 속한 스레드만 가능)

 뮤텍스 (mutex)

  공유 리소스에 단 하나에 스레드만 접근 (서로 다른 프로세스에 속한 스레드 가능)

 이벤트 (event)

  특정 사건의 발생을 다른 스레드에게 알림

 세마포어 (semaphore)

  한정도니 개수의 자원을 여러개의 스레드 사용시 접근 제어

 대기 가능 타이머 (waitable timer)

  특정 시간이 되면 대기중이던 스레드를 깨움



  스레드 동기화를 위해 스레드를 상태에 따라 구분 짓고 있다. Java 프로그램에서 스레드를 생성하게 된다면, JVM 에 있는 스케줄러는 스레드를 아래에 표시된 상태에 따라 분류시키고 작업을 수행할 스레드를 결정짓게 된다. 


New : 스레드를 선언, 아직 스레드 실행을 위한 준비는 되어 있지 않음

  • Runnable : 스레드 실행 준비, Start() 메소드가 실행되어 언제든지 다시 스레드가 작동이 가능함
  • Running : 스레드 동작, 스레드가 실제로 동작 하고 있는 상태
  • Blocking : 다른 스레드의 동작을 위해 스레드가 잠시 쉬고 있는 상태, Wait(), Sleep() 등..
  • Locked : 스레드 동기화를 위해 스레드의 공유 리소스 접근이 불가능하여 동작이 잠시 정지된 상태, 스레드 스케줄러에 의해 조정
  • Dead : 스레드가 작업을 모두 완료한 상태



2. 임계영역 (Critical Section)

  환전소에서 원화 금액과 달러를 환전 해줄 때 스레드를 이용한 프로그램을 개발 할 수 있다. 환전소 잔고에는 총 2000 달러와 1000000 원이 존재하고, 고객들은 환전소에서 원화 금액 혹은 외화 금액으로 잔액을 바꿔 갈 수 있다. 

Customer.java

  한명의 고객이 환전소에 올때마다 Customer 스레드가 존재한다. 이 스레드에서 환전소의 잔고에 접근 해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 
public class Customer extends Thread {
 
    public static int money;
    // true : USD / false : krw
    public static boolean curr;    
    
    public Customer(int money, int tcurr){
        Customer.money = money;
        
        if(tcurr == 1) curr = true;
        
    }
    
    @Override
    public void run(){
        if (curr){    // KRW(F) -> USD(T)
            MainThread.balance.krwtousd(money);
        }
        
        else {    // USD(T) -> KRW(F)
            MainThread.balance.usdtokrw(money);
        }
        
    }
}
 
cs


Balance.java

  환전소의 잔고 Class이다. 해당 클래스에는 원화 금액과 외화 금액의 남은 값이 존재한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 
public class Balance {
 
    //  잔고 현재 금액
    private static int KRW = 1000000;
    private static int USD = 2000;
    
    // 당일 환율
    private static int CURR = 1200;
    
    public static int getKRW() {
        return KRW;
    }
 
    public static void setKRW(int kRW) {
        KRW = kRW;
    }
    
    public static int getUSD() {
        return USD;
    }
 
    public static void setUSD(int uSD) {
        USD = uSD;
    }
    
    // KRW to USD
    public void krwtousd (int kRW){
        int tempUSD = kRW / CURR;
        USD = USD - tempUSD;
        KRW = KRW + kRW;
    }
    
    // KRW to USD
    public void usdtokrw (int uSD){
        int tempKRW = uSD * CURR;
        USD = USD + uSD;
        KRW = KRW - tempKRW;
    }    
 
}
 
cs


MainThread.java

  메인 메소드가 포함된 클래스이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import java.util.Scanner;
 
public class MainThread {
 
    public static Balance balance = new Balance();
    
    public static void main(String[] args) throws Exception {
        // TODO Auto-generated method stub
        
        Scanner sc = new Scanner(System.in);
        
        System.out.println("총 고객의 수를 입력하시오.");
        int customerNum = sc.nextInt();    // 총 고객의 수
 
        
        for(int i=0; i<customerNum; i++){
            System.out.println("각 고객의 환전액과 지급 받기 원하는 통화를 입력하시오 (0:KRW / 1:USD)");
            
            int money = 0;
            int curr = 0;
            while(true){
                money = sc.nextInt();    // 금액
                curr = sc.nextInt();    // 통화
                
                if(curr != 0 && curr != 1System.out.println("통화를 잘못 입력하였습니다.");
                else break;
                
            }
 
            Customer customer = new Customer(money,curr);
            customer.start();
        }
        
        System.out.println("현재 잔고 USD :"+balance.getUSD()+" KRW :"+balance.getKRW());
        
    }
 
}
 
cs


  위와 같이 설계해도 원하는 값이 나올 수 있다. 만약 메인 클래스에서 스레드가 순차적으로 잘 생성 되면 우리가 원하는 대로 값을 그대로 가져온다. 하지만 어떤 스레드가 먼저 실행 될지 우리는 알 수 없다. 따라서 임계영역을 지정하여 공유 리소스에 스레드가 접근 할 때 다른 스레드가 접슨 할 수 없도록 제어해야한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
    // KRW to USD
    public synchronized void krwtousd (int kRW){
        int tempUSD = kRW / CURR;
        USD = USD - tempUSD;
        KRW = KRW + kRW;
    }
    
    // KRW to USD
    public synchronized void usdtokrw (int uSD){
        int tempKRW = uSD * CURR;
        USD = USD + uSD;
        KRW = KRW - tempKRW;
    }    
 
cs


  이런 식으로 우리가 공유 리소스를 사용하고자 하는 부분에 synchronized 키워드를 를 추가하게 되면 임계 영역(Critical Section)을 지정할 수 있다.


3. 스레드 동기화 원리

  단순히 synchronized 키워드 추가로 공유 리소스 영역을 제외할 수 있다면 참 좋겠지만, 공유 리소스와 스레드는 개발자의 영역은 아니기에 제어가 쉽지 않다. 공유 리소스는 Heap 영역에 존재하고, 스레드는 JVM 영역에서 작동한다. 따라서 앞서 synchronized 키워드의 사용은 Heap 영역에서 동기화가 작동하게 된다.


  하지만 synchronized 동기화에는 치명적인 문제점 3가지가 존재한다.

    1. JVM에서 성능 저하가 발생하며 코드의 속도가 느려질 수 있다. (최근에는 많이 해결 되었다)

    2. 데드락이 발생할 가능성이 있다.

    3. 동시 변경이나 접근으로부터 보호하기 위해 항상 객체 자체를 보호하지는 않고, 실제로 객체를 보호하지 못하는 경우도 있다.

  또한, 스레드를 사용하는 방법에는 2가지가 있다.


- 동기화 메소드

메소드 수준에서 관리하는 동기화


1
2
3
4
5
6
7
8
9
10
11
12
13
class theObject
 
{
 
  synchronized public void method()
 
  {
 
    statement;
 
  }
 
}
cs


- 동기화 블록

블록으로 관리하는 동기화


1
2
3
4
5
6
7
8
9
10
11
12
13
synchronized( theObject )
 
   statement;       // theObject 가 동기화된다.
 
     
 
synchronized( theObject )
 
{
 
   statement;      // theObject 가 동기화된다.
 
}
cs



4. 메소드를 이용한 동기화

  실제로는 Synchronized 키워드를 사용하고도 동기화가 제대로 이뤄지지 않는 점을 극복하기 위해서 조금 더 확실한 스레드 제어 방법이 필요하다. 대표적으로 사용하는 방식이 스레드 Class에서 제공하는 메소드를 이용하면 된다.


notify() : 우선순위가 높은 스레드 하나를 깨운다.

notifyall() : 대기중인 스레드를 전부 깨운다.

yield() : 진행중인 스레드에 대한 양보를 실시한다.

wait() : 스레드를 대기 시킨다.

  만약 앞서 보여준 프로그램에 메소드를 이용하면 아래와 같이 사용 할 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
 
public class Customer extends Thread {
 
    public static int money;
    // true : USD / false : krw
    public static boolean curr;    
    
    public Customer(int money, int tcurr){
        Customer.money = money;
        
        if(tcurr == 1) curr = true;
        
    }
    
    @Override
    public void run(){
        synchronized(this){
            try{
            
                if (curr){    // KRW(F) -> USD(T)
                    MainThread.balance.krwtousd(money);
                }
                
                else {    // USD(T) -> KRW(F)
                    MainThread.balance.usdtokrw(money);
                }
            
            } catch(Exception e) {
                System.err.println(e.getMessage());
 
            }
            notify();
        }
        System.out.println("현재 잔고 USD :"+MainThread.balance.getUSD()+" KRW :"+MainThread.balance.getKRW());
        
    }
    
}
 
cs


'Skill > Programming - Network' 카테고리의 다른 글

9. URLConnection 클래스  (0) 2019.06.16
8.인터넷 주소 클래스 (InetAddress Class)  (0) 2019.06.02
6. 스레드 폴링, 콜백  (0) 2019.05.07
5. 스레드(Thread)  (0) 2019.04.27
4. 프로세스 관리 (Process Control)  (1) 2019.04.07
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함