Synchronized
여러개의 스레드가 한개의 자원을 사용하고자 할 때, 현재 데이터를 사용하고 있는 해당 스레드를 제외하고 나머지 스레드들은 데이터에 접근 할 수 없도록 막는 개념이라고 보면 된다.
Synchronized는 메서드와 변수에 선언하여 사용이 가능하다.
// 1. 메서드에서 사용하는 경우
public synchronized void method(){
// 코드
}
// 2. 객체 변수에 사용하는 경우
private Object obj = new Object();
public void exampleMethod(){
synchronized(obj){
//코드
}
}
동기화가 안되어 있을 때 발생할 수 있는 문제는 어떤게 있을까?
가장 대표적으로 은행 계좌에 연결하여 생각할 수 있다.
아래의 코드를 보자
package ThreadSynchronized;
public class ThreadSynchronizedTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
Task task = new Task();
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.setName("t1-Thread");
t2.setName("t2-Thread");
t1.start();
t2.start();
}
}
class Account{
int balance = 1000;
public void withDraw(int money){
if(balance >= money){
try{
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + " 출금 금액 ->> "+money);
Thread.sleep(1000);
balance -= money;
System.out.println(thread.getName()+" balance:" + balance);
}catch (Exception e) {}
}
}
}
class Task implements Runnable{
Account acc = new Account();
@Override
public void run() {
while(acc.balance > 0){
// 100, 200, 300 중의 한 값을 임의로 선택해서 출금(withDraw)한다.
int money = (int)(Math.random() * 3 + 1) * 100;
acc.withDraw(money);
}
}
}
Account 라는 클래스에는 balance(잔액)이 있고, 이 잔액을 지속적으로 인출을 하는 인출메서드가 있다.
Runnable을 구현하여 만든 Task에 100,200,300 중 랜덤하게 값을 전달받아 moneny 변수에 할당하고 그 금액만큼 Account 인스턴스의 인출메서드를 호출해 balance (잔액)을 출금한다.
다음 main 메서드에서 스레드 t1, t2를 만들고 각각의 스레드 이름을 정의한다.
t1, t2 스레드가 시작하면 잔액이 0이 될 때까지 두 스레드가 경쟁하며 출금이 시작되는 것을 확인할 수 있다.
결과화면
결과를 보면 분명 잔액이 인출할 금액 이상일때만 인출이 되도록 했지만 잔액이 마이너스가 될 때까지 인출이 된 것을 확인할 수 있다.
여기서 멀티스레드의 문제점을 알 수 있다.
balance(잔액) thread-safe가 되지 않았기 때문에 아래와 같은 로직이 진행된다.
- t1 스레드가 잔액을 삭감하기 전에
- t2가 balance(잔액)에 접근해 삭감을 해버리고
- 다시 t1이 slee()에서 깨어나 balance(잔액) 을 삭감
그렇게 잔액이 0 이하의 마이너스 값을 가지게 된다.
해결방법
해결방법은 의외로 간단하다.
공유데이터에 대한 접근과 수정이 이루어지는 메서드에 synchronized 키워드를 리턴타입 앞에 붙여주면 된다.
- t1스레드가 먼저 공유데이터나 메서드에 점유하고 있는 상태인 경우 block으로 처리하기 때문에 t1 이외의 스레드의 접근을 막는다.
- t1스레드가 작업을 다 끝냈다면 .unblock으로 처리하여 t1 이외의 스레드의 접근을 허락한다.
package ThreadSynchronized;
public class ThreadSynchronizedTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
Task task = new Task();
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.setName("t1-Thread");
t2.setName("t2-Thread");
t1.start();
t2.start();
}
}
class Account{
int balance = 1000;
public synchronized void withDraw(int money){
if(balance >= money){
try{
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + " 출금 금액 ->> "+money);
Thread.sleep(1000);
balance -= money;
System.out.println(thread.getName()+" balance:" + balance);
}catch (Exception e) {}
}
}
}
class Task implements Runnable{
Account acc = new Account();
@Override
public void run() {
while(acc.balance > 0){
// 100, 200, 300 중의 한 값을 임의로 선택해서 출금(withDraw)한다.
int money = (int)(Math.random() * 3 + 1) * 100;
acc.withDraw(money);
}
}
}
결과화면
synchronized 키워드를 사용함으로써 balance 공유데이터에 대한 thread-safe를 시켰기 때문에
데이터나 메서드 점유하고 있는 스레드가 온전히 자신의 작업을 마칠 수 있게 되었다.
'Backend > JAVA' 카테고리의 다른 글
[Java] 컬렉션 프레임워크(Collection Framework) 란? (0) | 2023.02.07 |
---|---|
[JAVA] Thread와 Runnable (0) | 2023.02.05 |
[JAVA] 정규표현식(Regular expression) (1) | 2023.02.01 |
[JAVA] try-with-resources란? (0) | 2023.01.31 |
[JAVA] 인터페이스와 추상 클래스의 객체 생성은 불가능? (0) | 2022.10.19 |