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