티스토리 뷰

요즘 새롭게 이직한 회사에서 메시지큐라던가 이벤트 핸들이라던가 새로운 것을 많이 배우고 있다.

그 중 하나가 바로 syncronized이다.

 

아무래도 서비스에서 비동기적으로 처리하는 부분이 많다보니까 비동기적인 로직에서 싱크를 맞춰야 하는 부분은 싱크를 맞추기 위해서 syncronized를 사용하는 것이다.

 

 

syncronized란?

멀티 쓰레드와 같이 비동기 처리를 하다보면 그 로직 중에서 특정 부분을 동기 처리하는데 사용하는 키워드다.

메소드에 키워드를 붙여서 사용하기도 하고 syncronized 블록을 만들어서 그 안을 동기 처리 하기도 한다.

syncronized 키워드를 사용해서 동기 처리라고 명시 해준 부분은 한 쓰레드가 접근했을 때 해당 영역을 lock 처리하고 다른 쓰레드는 접근해 있는 쓰레드가 unlock 하기 전까지는 해당 영역에 접근을 하지 못한다.

 

 

비동기 코드 예

syncronized 키워드를 사용하기 위해서 Thread를 여러 개 만들어서 비동기 처리 되는 코드를 하나 작성해봤다.

간단하게 index라는 전역 정수 변수를 0부터 10까지 하나 씩 올리면서 print하는 것이다.

 

 

우선 index 변수를 0부터 10까지 하나 씩 올리면서 print 하는 것은 따로 ThreadTask라는 클래스를 만들어 Runnable을 구현해 만들어봤다.

public class ThreadTask implements Runnable {
	int index = 0;
	
	@Override
	public void run() {
		Thread thread = Thread.currentThread();
		
		printIndex(thread.getName());
	}
	
	private void printIndex(String threadName) {
    		index = 0;
        
		while(index <= 10) {
			System.out.println(String.format("==> %s current index: %d", threadName, index));
			index ++;			
		}
	}
}

 

 

ThreadTask를 받아서 동작하는 스레드는 2개를 생성해서 실행을 시켜봤다.

public void syncronizedTest() {	
	ThreadTask task = new ThreadTask();
		
	Thread thread1 = new Thread(task);
	Thread thread2 = new Thread(task);
		
	thread1.start();
	thread2.start();
}

 

 

실행시키면 아래와 같이 전역 변수인 index를 두 스레드가 공유해서 index가 0부터 10까지 무작위로 두 스레드에서 찍힌다.

두 스레드가 비동기적으로 전역 변수 index에 접근해서 무작위로 사용하기 때문에 이와 같이 index의 값이 무작위로 찍히는 것이다.

 

 

syncronized로 동기 영역 만들기

그렇다면 이제 syncronized 키워드를 통해서 index가 순차적으로 잘 찍히도록 만들어보자.

 

syncronized는 위에서 얘기했듯이 메서드에 키워드를 붙일 수 있고 또는 syncronized 블록을 만들어서 동기 영역을 만들 수 있다.

ThreadTask에 printIndex에 아래와 같이 syncronized 키워드를 붙일 수 있다.

public class ThreadTask implements Runnable {
	...
	
	private synchronized void printIndex(String threadName) {
		index = 0;
		
		while(index <= 10) {
			System.out.println(String.format("==> %s current index: %d", threadName, index));
			index ++;			
		}
	}
}

 

 

syncronized 키워드를 붙이고 아까와 같이 두 스레드를 한꺼번에 실행시켜보면 아래와 같이 두 스레드가 순차적으로 실행되는 것을 확인할 수 있다.

 

syncronized 키워드를 붙여서 아래와 같이 printIndex 함수가 동기 영역이 된 것이다.

그래서 먼저 실행된 thread1에서 printIndex를 lock 해서 먼저 사용하고 사용이 끝난 후에 unlock 되고 thread2가 사용이 끝난 printIndex를 사용한 것이다.

 

 

synchronized 키워드는 이와 같이 메소드에 붙일 수도 있고 아래와 같이 객체를 넣어서 따로 synchronized 블록을 만들 수 있다. 여기서는 아예 this를 통해서 ThreadTask 자체를 넣어서 ThreadTask 전체를 동기 영역으로 만들었다. 

public class ThreadTask implements Runnable {
	..
	
	private void printIndex(String threadName) {
		synchronized (this) {
			index = 0;
		
			while(index <= 10) {
				System.out.println(String.format("==> %s current index: %d"
                						  , threadName, index));
				index ++;			
			}
		}
	}
}

 

 

synchronized 블록의 괄호 안에는 객체가 들어갈 수 있다. 그래서 보통 synchronized 블록 괄호 안에는 동기로 사용해야하는 전역 객체 등을 넣어서 사용한다.

int의 경우 객체가 아니라 단순 자료형이기 때문에 만일 synchronized 블록 안에 넣기 위해서는 Integer 객체로 선언을 해야한다. synchronized 괄호 안에 들어간 것은 동기 영역의 기준이 되는 것으로 해당 객체에 lock을 걸 수 있다.

 

 

synchronized에 index를 넣어서 동작하는 것을 제대로 확인하기 위해 printIndex의 시작과 끝 그리고 index를 init 하는 부분에서 콘솔에 메시지를 찍어보도록 했다. 또한 index를 init 하는 코드를 synchronized 밖으로 빼내 비동기로 thread1과 thread2에서 비동기적으로 사용하도록 했다.

public class ThreadTask implements Runnable {
	Integer index = 0;
	
	@Override
	public void run() {
		Thread thread = Thread.currentThread();

		printIndex(thread.getName());
	}
	
	private void printIndex(String threadName) {
		System.out.println(String.format("==> %s printIndex start", threadName));
		
		System.out.println(String.format("==> %s printIndex index init", threadName));
		index = 0;
		
		synchronized (this.index) {
			while(index < 5) {
				System.out.println(String.format("==> %s current index: %d"
                								, threadName, index));
				index ++;
			}
		}
		
		System.out.println(String.format("==> %s printIndex end", threadName));
	}
}

 

 

동작을 시켜보다보면 thread1과 thread2가 서로 index를 잘 lock 걸면서 동작할 때도 있지만 어떨 때는 아래와 같이 꼬여서 동작할 때가 있다. 바로 index init 하는 부분이 synchronized 블록 밖에 있어서 생기는 일이다.

 

1. thread2가 시작하고 index를 init한다.

2. thread1이 시작하고 index를 init 한다.

3. thread2가 먼저 synchronized 블록으로 들어가 index에 lock을 건다. 들어가서 반복문을 통해 index를 4까지 올리고 콘솔에 찍는다. 마지막으로 index를 5로 올리고 unlock 한다.

4. 뒤를 이어 thread1이 unlock된 반복문으로 들어가서 lock을 걸고 로직을 수행하려고 하지만 이미 index는 thread2로 5가 되어 있어 바로 반복문을 나오고 unlock하고 end 문구를 콘솔에 찍는다.

 

 

좀 복잡하지만 synchronized 블록으로 인해서 위와 같은 동작이 가능한 것이다. index를 기준으로 lock을 걸어서 뒤를 이어서 수행하려고 했던 thread2는 index를 0으로 init하는 코드는 비동기적으로 이미 처리를 해서 5가 된 index를 다시 0으로 init하고 반복문을 수행할 수 없는 것이다.

 

 

 

synchronized 키워드. 아직 익숙하지는 않지만 동기 영역을 나누기 위한 키워드라는 것은 확실히 알 수 있었다.

단, synchonized 키워드를 사용할 경우 스레드끼리 lock과 unlock을 반복하는데 이는 리소스가 들어가고 또한 synchronized 블록을 너무 크게 잡아서 비동기로 처리해도 될 것을 동기로 처리하는 잘못도 범할 수 있다. 따라서 synchronized 키워드를 너무 남발하지 않도록 주의해야 한다.

 

 

 

 

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함