ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Java | Thread (3) Synchronization 동기화
    JAVA/JAVA 2020. 2. 11. 17:06

    Synchronization 동기화

    하나의 자원을 여러 태스크가 사용하려고할 때
    한 시점에서 하나의 태스크만 사용할 수 있도록 하는 것

    • 쉽게 얘기하면 A, B 스레드가 한 공간에서 동시에 작업을 하려고할 때
      A 스레드의 작업이 끝날 때까지 B가 기다려주는 것(반대의 경우도 마찬가지)
    • 대부분의 응용 프로그램에서 다수의 스레드가 공유할 수 있는 영역이 요구됨
    • 공유하는 부분은 상호 배타적으로 사용되어야 함
    • ex) 좌석 예매 시스템
      판매처가 2곳 이상일 때 동기화가 되지 않는다면
      같은 좌석을 여러 명의 사람에게 판매하는 경우가 생김
    • DB연동시 많이 사용(조회, 수정, 삭제)
      트랜잭션과 연관이 있음(commit, rollback)

    Critical Section 임계 영역

    • 상호배타적으로 사용되는 공유 영역
    • 자바는 한 순간에 하나의 스레드만 실행하도록 하는 synchronized method 제공
    • 한 스레드가 synchronized method를 수행 중이면 다른 스레드는 대기함

    synchronized 예약어

    메소드에 Lock을 걸어서 동기화를 도와주는 예약어


    형식 ①

    메소드 자체에 Lock을 거는 경우

    접근 제어자 synchronized 반환형 메소드명(){}

    ※ 해당 메소드를 A스레드에서 실행중이면 B스레드는 A의 작업이 끝날때 까지 대기


    예제

    입출력(RandomAccessFile)과 Thread의 동기화를 활용한 예제
    RandomAccessFile: 특정위치에 접근해서 파일을 읽거나 쓰는 입출력 클래스

    package j200211;
    
    import java.io.*;
    import java.lang.Thread;
    
    public class ShareTest implements Runnable {
    
        // RandomAccessFile 클래스형 변수 선언
        RandomAccessFile raf = null;
    
        public ShareTest() {
            try {
                // 파일 생성, 읽기/쓰기가 가능하도록 rw 모드로 설정
                raf = new RandomAccessFile("result.txt", "rw");
    
                // Thread 객체 생성, this는 Runnable 구현 클래스인 ShareTest 클래스를 지칭
                Thread t1 = new Thread(this, "MJH");
                Thread t2 = new Thread(this, "TEST");
    
                // Thread 객체 실행
                t1.start();
                t2.start();
    
            } catch (Exception e) {
                System.out.println(e);
            }
        }
    
        // 동기화 → synchronized 예약어로 run() 메소드에 lock 걸기 
        public synchronized void run() {
            // writeBytes(), raf에 글자쓰기
            try {
                for (int i = 0; i < 1000; i++) {
                    // Thread 명 + File Pointer 위치 + 줄바꿈(\r\n: 윈도우 운영체제 줄바꿈)
                    raf.writeBytes(Thread.currentThread().getName() + "[" + i + "]" + raf.getFilePointer() + "\r\n");
                }
            } catch (Exception e) {
                System.out.println(e);
            }
    
        }
    
        public static void main(String[] args) {
    
            // ShareTest 객체 생성
            new ShareTest();
            /*
            MJH[0]0
            MJH[1]9
            MJH[2]18
            ....
            */
        }
    
    }

    ※ MJH 스레드가 작업을 전부 마친 뒤에 TEST 스레드가 작업을 시작함


    형식 ②

    메소드 내부의 특정 부분에만 Lock을 거는 경우

    synchronized(공유 데이터){
        // Java Code;
    }

    ※ DB연동시 많이 사용하는 형식


    예제

    급여계좌(공유 데이터)에서 ATM이 보험금과 공과금을 일정 금액씩 인출하는 프로그램 작성

    package j200211;
    
    class ATM implements Runnable {
    
        // 급여계좌 잔액
        private long money = 10000;
    
        public void run() {
            // 동기화 → ATM 객체에 복수의 스레드가 접근할 때 서로 기다리도록 함
            synchronized (this) {
                for (int i = 0; i < 5; i++) {
                    try {
                        // 스레드가 1초간 대기하도록 함
                        Thread.sleep(1000);
                    } catch (Exception e) {
                        System.out.println(e);
                    }
                    // 잔액이 0보다 작아지면 반복문에서 탈출
                    if (check() <= 0)
                        break;
                    // 출금
                    withdraw(1000);
                }
            }
        }
    
        // 인출 메소드
        public void withdraw(long money) {
            // check() - money > 0 인 경우에 출금
            if (check() > money) {
                this.money -= money;
                // 현재 Thread 및 잔액 출력
                System.out.println(Thread.currentThread().getName() + ", " + check());
            } else {
                System.out.println("잔액이 부족합니다!");
            }
        }
    
        // 잔액 조회
        public long check() {
            return money;
        }
    
    }
    
    public class SyncTest {
    
        public static void main(String[] args) {
            // 객체를 하나만 생성해야 여러 스레드에서 하나의 데이터를 공유할 수 있음
            ATM atm = new ATM();
    
            // Thread 생성 및 실행
            Thread atm1 = new Thread(atm, "보헙금");
            Thread atm2 = new Thread(atm, "공과금");
            atm1.start();
            atm2.start();
            /*
            보헙금, 9000
            보헙금, 8000
            보헙금, 7000
            보헙금, 6000
            보헙금, 5000
            공과금, 4000
            공과금, 3000
            공과금, 2000
            공과금, 1000
            잔액이 부족합니다!
            */
        }
    
    }

    두 개 이상의 스레드를 사용할 때 하나의 공유 데이터에 대하여
    각 스레드가 배타적으로 작업을 수행하도록 하는 동기화에 대해 살펴보았습니다.

    우선순위를 부여하는 것보다도 확실하게 스레드의 작업을 분리할 수 있으며

    DB와 연동하거나 작업이 동시에 이루어지면 안 되는 메소드가 있는 경우에
    Synchronized 예약어를 사용할 수 있겠습니다.

    댓글

Designed by Tistory.