synchronized에 대해 알아보자

KKTRKKT·2022년 4월 11일
0

자바

목록 보기
3/3

개요

synchronized는 자바 예약어로 동기화를 위해 사용한다. 동기화하는 이유는 스레드의 동시성 문제 및 가시성 문제를 해결하기 위해서다. 여기서는 동시성 문제만 다뤄본다. 동시성 문제라는건 두 개 이상의 스레드가 변경 가능한 공유데이터를 동시에 바꾸려고 할때 생기는 문제다.

예를 들어 a라는 정적 변수를 0으로 초기화하고, 200,000을 만들기 위해 2개에 스레드에서 100,000씩 더한다고 하면 200,000이라는 값이 나오지 않는다. 왜냐하면 스레드가 차례대로 +1을 하는게 아니라 동시에 값을 수정하기 때문이다.

public class ConcurrencyTest{
    static int a = 0; // a 변수 초기화

    public static void main(String[] args) throws InterruptedException{
        new Thread(()->{for(int i=0;i<100000;i++) a++).start(); // 스레드를 생성하고 a+1 10만 번 반복
        new Thread(()->{for(int i=0;i<100000;i++) a++).start(); // 스레드를 생성하고 a+1 10만 번 반복
        Thread.sleep(100); // 결과 출력을 위해 멈춤
        System.out.println(a); // 총 20만번을 더했으므로 20만이 나올것으로 기대
    }
}

결과 :

실행할때 마다 랜덤한 결과값이 나온다.

덧붙이자면 변경 가능한 공유데이터는 JVM의 Runtime Data Areas 의 다섯 영역(Method Area, Heap, Stack, Pc Register, Native Method Stack) 중 method area와 heap 영역이다. Method Area에는 정적 변수가 들어가고, Heap에는 참조 레퍼런스들이 들어간다.

동기화란?

동기화는 위에서 말했듯이 멀티스레드의 동시성 문제 및 가시성 문제를 해결하기 위한 방법이다.
쓰레드 동기화를 하기 위해서는 임계영역(critical section)과 락(lock)을 사용한다. 임계영역으로 설정한 구역은 동시에 리소스를 사용할 수 없는 구역이고, 락을 획득한 쓰레드에 대해서만 리소스를 사용하도록 하는 방식이다.

자바의 모든 객체는 락을 갖고 있다. 모든 객체가 갖고 있으니 고유 락(intrinsic lock)이라고도 하고, 모니터처럼 동작한다고 하여 모니터 락(monitor lock) 혹은 그냥 모니터(monitor)라고 불린다.

synchronized 사용법

위에 결과처럼 멀티스레드에서는 동시성 문제가 발생하므로, synchronized 예약어를 사용해 해결해보자.
synchronized는 다음 세 영역에서 사용할 수 있다.

 static int a = 0;

인스턴스 메서드

 private synchronized void increase(){
     a++;
 }

인스턴스 메서드를 가지고 있는 클래스에 의해서 동기화된다. 즉 해당 인스턴스의 메소드는 하나의 스레드만 접근 가능하다고 보면 된다.

정적 메서드

 private synchronized void increase(){
     a++;
 }

클래스에 의해서 동기화된다. 클래스는 JVM에 하나만 존재하므로, 인스턴스에 상관없이 메서드도 하나만 존재한다.

코드 블록

 private void increase(){
 	synchronized(this){
		a++;
    }
 }
 private static void increase(){
	synchronized(SynchronizedTest.class){
		a++;
   }
}

메서드를 동기화하면 하나의 스레드가 메서드를 실행하는 동안 다른 스레드는 대기중이기 때문에, 비효율적이고 자원낭비가 심할 수 있다. 이때 메소드 내 블록 동기화를 사용해 동기화 부분을 최소화함으로서 성능하락을 완화할 수 있다.

synchronized() 괄호 안에는 모니터 객체, 즉 객체가 들어가고 해당 객체에서 동기화된다. 모니터 객체당 하나의 스레드만 해당 코드 블록 내에서 실행할 수 있다는 얘기다.

결과

 public static void main(String[] args) throws InterruptedException{
 	SynchronizedTest test = new SynchronizedTest();
	new Thread(()->{for(int i=0;i<100000;i++) test.increase();}).start();
	new Thread(()->{for(int i=0;i<100000;i++) test.increase();}).start();
	Thread.sleep(100);
	System.out.println(a); // 200,000
 }

동기화를 해줬기 때문에 항상 일정하게 200,000이라는 결과값이 나오게 된다.

재진입

private void reentrancy(){
        synchronized(this){
            System.out.println("enter");
            synchronized(this){
                System.out.println("reenter");
            }
        }
    }

재진입은 이미 락을 획득한 스레드가 같은 락을 계속해서 획득할 수 있다는 것을 의미한다. 같은 락에 대한 synchronized 블록을 만났을 때 대기없이 통과한다.

그만 알아보자

synchronized를 사용해서 동시성 문제를 해결할 수 있으며, 인스턴스 메서드, 정적 메서드, 코드 블록 영역에서 사용할 수 있다.

profile
https://kktrkkt.github.io/

0개의 댓글