메뉴 건너뛰기

2016.09.13 18:50

쓰레드의 동기화

조회 수 3315 추천 수 0 댓글 0
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄

쓰레드의 동기화


싱글쓰레드 프로세스의 경우 프로세스 내에 단 하나의 쓰레드만 작업하기 때문에 프로세스의 자원을 가지고 작업하는 데 별문제가 없지만, 멀티쓰레드 프로세스의 경우 여러 쓰레드가 같은 프로세스 내의 자원을 공유해서 작업을 하기 때문에 서로의 작업에 영향을 주게 된다. 만일 쓰레드A가 작업하던 도중에 다른 쓰레드B에게 제어권이 넘어갔을 때, 쓰레드A가 작업하던 공유 데이터를 쓰레드B가 임의로 변경하였다면, 다시 쓰레드A가 제어권을 받아서 나머지 작업을 마쳤을 때 원래 의도했던 것과는 다른 결과를 얻을 수 있다.

이는 마치 한 방의 여러 사람이 방안의 컴퓨터를 함께 나눠 쓰는 상황과 같아서 한 사람이 컴퓨터로 문서작업 도중에 잠시 자리를 비웠을 때 다른 사람이 컴퓨터를 만져서 앞 사람이 작업하던 문서가 지원진다던가 하는 일이 생길 수 있다. 이럴 때는 문서작업이 끝날 때까지는 컴퓨터에 비밀번호를 걸어서 다른 사람이 사용할 수 없도록 해야 한다.

이처럼 멀티쓰레드 프로그래밍에서 동기화는 중요한 요소이다. 얼마만큼 동기화를 잘 처리하는가에 따라서 프로그램의 성능에 많은 영향을 미치게 된다.


1. synchronized를 이용한 동기화

자바에서는 키워드 synchronized를 통해 해당 작업과 관련된 공유 데이터에 lock을 걸어서 먼저 작업 중이던 쓰레드가 작업을 완전히 마칠 때까지는 다른 쓰레드에게 제어권이 넘어가더라도 데이터가 변경되지 않도록 보호함으로써 쓰레드의 동기화를 가능하게 한다.


(1) 특정 객체에 lock을 걸고자 할 때

synchronized(객체의 참조변수){

//.....

}

synchronized블록의 경우 지정된 객체는  synchronized블럭의 시작부터 lock이 걸렸다가 블록이 끝나면 lock이 풀린다. 이 블록을 수행하는 동안은 지정된 객체에 lock이 걸려서 다른 쓰레드가 이 객체에 접근할 수 없게된다.


(2) 메서드에 lock을 걸고자 할 때

public synchronized void calcSum(){

//.....

}

synchronized 메서드의 경우에도 한 쓰레드가 synchronized 메서드를 호출해서 수행하고 있으면, 이 메서드가 종료될 때까지  다른 쓰레드가 이 메서드를 호출하여 수행할 수 없게 된다.


2. 예제

class ThreadEx24 {

public static void main(String args[]) {

Runnable r = new A();

Thread t1 = new Thread(r);

Thread t2 = new Thread(r);


t1.start();

t2.start();

}

}


class Account {

int balance = 1000;


public void withdraw(int money){

if(balance >= money) {

try { Thread.sleep(1000);} catch(Exception e) {}

balance -= money;

}

} // withdraw

}


class A implements Runnable {

Account acc = new Account();


public void run() {

while(acc.balance > 0) {

// 100, 200, 300중의 한 값을 임으로 선택해서 출금(withdraw)

int money = (int)(Math.random() * 3 + 1) * 100;

acc.withdraw(money);

System.out.println("balance:"+acc.balance);

}

} // run()

}

실행결과)

balance:700

balance:500

balance:200

balance:200

balance:0

balance:-100

실행결과를 보면 잔고(balance)가 음수인 것을 알 수 있다. 그 이유는 한 쓰레드가 if문의 조건식을 통과하고 출금하기 바로 직전에 다른 쓰레드가 끼어들어서 출금을 먼저 했기 때문이다.

에를 들어 한 쓰레드가 id문의 조건식을 통과하고 출금하기 바로 직전에 다른 쓰레드가 끼어들어서 출금을 먼저 했기 때문이다. 예를 들어 한 쓰레드가 if문의 조건식을 계산했을 때는 잔고(balane)가 200이고 출금하려는

금액(money)이 100이라서 조건식(balance >= money)이 true가 되어 출금(balance -= money)을 수행하려는 순간 다른 쓰레드에게 제어권이 넘어가서 다른 쓰레드가 200을 출금하여 잔고가 0이 되었다.

다시 이전의 쓰레드로 제어권이 넘어오면 if문 다음부터 수행하게 되므로 확인하는 if문과 출금하는 문장은 하나로 동기화블록으로 묶어져야 한다.

예제에서는 상황을 보여주기 위해 일부러 Thread.sleep(1000)을 사용해서 if문을 통과하자마자 다른 쓰레드에게 제어권이 넘기도록 하였지만, 굳이 이렇게 하지 않더라도 쓰레드의 작업이 다른 쓰레드에 의해서 영향을 받는 일이 발생할 수 있기 때문에 동기화가 반드시 필요하다.


class ThreadEx24 {

public static void main(String args[]) {

Runnable r = new A();

Thread t1 = new Thread(r);

Thread t2 = new Thread(r);


t1.start();

t2.start();

}

}


class Account {

int balance = 1000;


public synchronized void withdraw(int money){ //synchronized 키워드를 붙이기만 하면 간단히 동기화가 된다.

if(balance >= money) {

try { Thread.sleep(1000);} catch(Exception e) {}

balance -= money;

}

} // withdraw

}


class A implements Runnable {

Account acc = new Account();


public void run() {

while(acc.balance > 0) {

// 100, 200, 300중의 한 값을 임으로 선택해서 출금(withdraw)

int money = (int)(Math.random() * 3 + 1) * 100;

acc.withdraw(money);

System.out.println("balance:"+acc.balance);

}

} // run()

}

실행결과)

balance:800

balance:700

balance:500

balance:300

balance:100

balance:100

balance:0

balance:0

한 쓰레드에 의해서 먼저 withdraw()가 호출되면, 종료될 때까지 다른 쓰레드가 withdraw()를 호출하더라도 대기상태에 머물게 된다. 즉, withdraw()는 한 순간에 단 하나의 쓰레드만 사용할 수 있다는 것이다.


cf.) 만일 withdraw()가 수행되는 동안 객체에 lock을 걸고자 한다면 다음과 같이 할 수도 있다.

public void withdraw(int money){

synchronized (this){

if(balance >= money) {

try { Thread.sleep(1000);} catch(Exception e) {}

balance -= money;

}

    }

  }// withdraw()


아래와 같이 어떠한 하나의 데이터(int x) 를 처리함에 있어 쓰레드로 작성되어 있다면 그 데이터를 관리하는 set, get 메서드는 함께 동기화를 걸어주는 것이 기존적인 사항이다.

class K extends Thread {

private int x = 100;

public synchronized void setX(int x) {

this.x += x;

}

public synchronized int getX() { 

return x;

}

public void run() {

synchronized (this) { // 지역 동기화

setX(200);//300 + 200

System.out.println("x = " + getX()); //500

}

}

}

public class Exam_04 {

public static void main(String[] ar) {

K kp = new K();

kp.start();

}

}

실행결과)

x = 300

여러개의 쓰레드가 동시에 실행되면서 문제가 발생한다. 여러개 쓰레드 수행 도중에 데이터 꼬임 발생한다. 하나의 쓰레드에 대해 만약에 여러 개가 동작해서 문제의 소지가 발생할 경우를 대비하여 동기화를 시켜준다.

누군가 run()이라는 메서드를 처리하는 동안에는 다른 사람은 run()이라는 메서드를 실행할 수 없다.


cf.) 동기화 메서드(메서드 전체)

public synchronized void run() {

// 지역 동기화(특정 범위에 하나의 메서드가 들어있을 경우)

// 지역을 지정하여, setX()나 getX() 메서드 두 가지(동기화 필요한 메서드만)에 대해 동기화를 걸 경우 사용하는 것이다.

synchronized (this) { 

setX(200);

System.out.println("x = " + getX()); 

}

}



List of Articles
번호 제목 날짜 조회 수
131 자바 대소문자 확인하는 방법 file 2023.02.15 128
130 HashMap 사용하기 file 2021.03.31 134
129 [객체 지향 언어의 이해] 업캐스팅과 다운캐스팅 file 2021.03.31 157
128 쓰레드 (Thread) 사용하기 file 2021.03.31 104
127 TCP 소켓 프로그래밍 01 - Server/Client 일대일 연결 file 2021.03.31 119
126 자바 String Class 문자열 처리 함수에 대한 정리 2021.03.31 106
125 자바 - 공백 문자 제거하기 (trim, replaceAll) file 2021.03.31 173
124 jstl <c:url value=""> 사용시 ;jsessionid= 붙는 현상 file 2021.03.31 228
123 Reflection을 활용한 메서드, 필드 값 불러오기. 2021.03.31 122
122 java에서 이전 URL 알아내기 2021.03.25 689
121 log4j에서 로그가 출력되지 않는 문제 수정 2021.03.25 405
120 Gmail 메일 서버를 이용해서 메일 보내기 file 2020.06.29 256
119 사용자의 IP를 가져오기 (IPv4) 2020.06.29 1687
118 XML to JSON , JSON to Map 2020.06.29 247
117 국제 시간에 따른 날짜 출력 2020.06.29 121
116 자바 랜덤 함수(Java random) file 2019.03.05 766
115 이클립스에서 같은 파일을 여러 편집창으로 띄우기 file 2019.03.05 677
114 이클립에서 FTP 접속하면서 Operation failed. File system input or output error 가 날때 file 2019.03.05 872
113 이클립에서 Javadoc 생성시 unmappable character for encoding MS949 에러가 발생할때 file 2019.03.05 730
112 이클립스 html, js 등등의 파일에서 에러표시 지우기 2019.03.05 1443
Board Pagination Prev 1 2 3 4 5 6 7 8 Next
/ 8

하단 정보를 입력할 수 있습니다

© k2s0o1d4e0s2i1g5n. All Rights Reserved