JAVA/JAVA

Java | Thread (3) Synchronization 동기화

pathas 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 예약어를 사용할 수 있겠습니다.