ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Camel][Java] Synchronization ( 동기화 )
    Java/개념 정리 2020. 3. 3. 17:23

    Synchronization( 동기화 )란?

      동기화란, 한 쓰레드가 인스턴스에 접근해서 연산을 완료할 때까지, 다른 쓰레드가 인스턴스에 접근하지 못하도록 막는 것입니다. 우리가 일반적으로 쓰레드를 사용한 쓰레드 프로그래밍을 하다보면 하나의 인스턴스에 여러 개의 쓰레드가 접근하는 형태의 구현이 자주 요구됩니다. 이때 동기화를 해주지 않으면 문제가 발생하게되기 때문에 우리는 하나의 인스턴스에 여러 개의 쓰레드가 접근해야 할 때 동기화를 해줘야 합니다. 다만 예외적으로, StringBuffer와 같은 클래스는 이미 동기화 처리가 되어있기 때문에 여러 개의 쓰레드가 동시에 접근을 해도 문제가 없는 쓰레드에 안전한 클래스입니다.   

     

    Thread의 동기화 방법 

     Thread를 동기화 시키는 방법의 하나로 우리는 synchronized 키워드를 사용합니다. 이 키워드를 사용해 동기화 메소드를 선언하거나 동기와 블록을 지정할 수 있습니다.

     

    동기화 메소드 선언

    동기화 메소드의 경우는 아래의 예시처럼 접근제어지시자와 반환형 사이에 사용합니다. 

    public synchronized void method1 () { . . . }

    이렇게 메소드를 선언해주게 되면 한 쓰레드가 메소드를 실행중이라면, 이 메소드를 사용하고자하는 다른 쓰레드는 대기하고 있다가 완료가 된 후 메소드를 실행할 수 있습니다. 이러한 점은 실행시간을 길어지게 하면서 프로그램의 성능저하문제로 직결될 수 있습니다. 그렇기 때문에 필요한 위치에 제한적으로 사용해 성능에 영향을 주지 않도록 해야합니다. 

     

     하지만 그렇다면 아래의 코드와 같은 경우는 어떨까? 

    class Camel {
    	
        	int cnt = 0;
        
        	public synchronized int method1 ( int tall ) {
    		cnt++;
        		return tall+1;
    	}
    	public synchronized void method2 ( int tall ) {
     		cnt++;
        		return tall+10;
    	}
    }

    이 경우 method1과 method2가 동기화처리 되어있습니다. 이런 경우 method1이 동시에 호출되는 경우는 없을 것이고, method2 역시 동시에 호출될 수 없습니다. 그렇다면 method1이 호출됬을 때 method2가 호출될 수 있지않을까? 만약 두 메소드가 동시에 호출이 된다면 동기화를 해줬음에도 cnt라는 변수에 동기화 문제가 발생할 수 밖에 없습니다. 

     하지만 다행히도 이 경우 method1과 method2는 동시에 호출될 수 없습니다. 그 이유는 Java의 인스턴스에는 하나의 '열쇠'라는 것이 존재하고 이 열쇠를 사용해서 동기화 메소드를 호출해야하기 때문입니다. 열쇠는 하나뿐이기 때문에 동기화 메소드가 여러 개 일지라도 동시에 여러 개의 메소드가 실행될 수 없는 것입니다. 

     

    동기화 블록 지정

    앞서 동기화 메소드의 선언을 통해 동기화하는 방법을 설명했습니다. 하지만 동기화가 필요한 곳은 극히 일부분에 국한될 경우 이는 매우 비효율적일 수 밖에 없습니다. 동기화 메소드 전체의 실행이 완료되어야지만 열쇠를 반납하기 때문입니다. 이런 경우우리는 동기화 블록을 지정해 동기화의 대상을 메소드 전체가 아닌 코드의 일부로 제한함으로써 이 문제를 해결할 수 있습니다. 

    class Camel {
    	
        	int cnt = 0;
        
        	public int method1 ( int tall ) {
    		synchronized(this) { cnt++; }
        		return tall+1;
    	}
    	public void method2 ( int tall ) {
     		synchronized(this) { cnt++; }
        		return tall+10;
    	}
    }

     위의 코드는 동기화 블록을 지정해 동기화 대상을 코드 일부로 제한하는 예시입니다. 여기서 주목해야할 부분은 this 입니다. 동기화 메소드를 선언하는 방식에서는 우리는 열쇠를 선택할 수 없었습니다. 그러나 동기화 블록 지정 방식은 열쇠를 선택할 수 있습니다. 동기화 블록을 지정하는 synchronized(this) 구문의 괄호 안에는 열쇠를 입력할 수 있습니다. 열쇠는 모든 인스턴스에 한 개씩 존재하기에 아래와 같이 코드를 구성할 수도 있습니다. 

    class Camel {
    	
        	int cnt = 0;
            
         	Object key = new Object();
            
        	public int method1 ( int tall ) {
    		synchronized(key) { cnt++; }
        		return tall+1;
    	}
    	public void method2 ( int tall ) {
     		synchronized(key) { cnt++; }
        		return tall+10;
    	}
        
      
    }

    위 코드에서는 열쇠를 생성하기 위해 맨 아랫줄에 Object 인스턴스를 생성하고 있습니다. 이렇게 생성된 열쇠를 동기화 블럭을 지정하는 synchronized 구문의 괄호안에 넣어주면 됩니다. 이러한 방식은 모든 메소드가 하나의 열쇠로 동기화될 필요가 없을 때 사용합니다.

     

    동기화의 또다른 기능

     앞서 설명한 동기화의 기능은 쓰레드의 동시 접근을 막기 위한 것이었습니다. 하지만 그 외에도 우리는 동기화를 통해 쓰레드의 실행 순서를 조절할 수 있습니다.

     우리가 코딩할 하다보면 쓰레드의 실행순서가 중요한 상황이 있을 수 있습니다. 이 경우 코드의 순서를 바꿔서 해결할 수도 있지만, 쓰레드의 실행순서는 코드가 나열될 순서와 다를 수 있기 때문에 제대로된 해결책이 아닙니다. 이러한 쓰레드의 실행순서의 동기화를 위해 사용하는 것이 다음 메소드들입니다. 

     

    wait() - 쓰레드의 실행을 멈추고 대기한다

    notify() - 하나의 쓰레드를 깨운다.

    notfyAll() - 모든 쓰레드를 깨운다. 

     

     이 메소드들은 Object 클래스에 정의 되어 있고, 그렇기 때문에 어느 인스턴스를 대상으로도 호출이 가능합니다. 하지만 이 메소드들을 사용하면서 주의해야할 점은 wait 메소드와 notify(notifyAll) 메소드는 동기화 처리를 통해 한 순간에 하나의 쓰레드만 호출 가능하도록 해야한다는 점입니다. 이 두 메소드는 서로 다른 두 쓰레드에 의해 동시에 각각 호출되는 것 조차 불가능합니다. 따라서 이 메소드들을 사용하기 위해서는 동기화 블록 또는 동기화 메소드를 사용해 메소드 호출문장을 동기화 처리해야한다는 점입니다. 

     그리고 wait 메소드의 경우 연이은 호출이 가능합니다. wait 메소드가 호출될 경우 쓰레드는 더 이상 실행중이 아니고 멈춰있는 상태이기 때문에, 이때 다른 쓰레드가 동기화불록에 접근하는 것을 허용하게됩니다. 즉 이말은 다른 쓰레드에서 wait 메소드를 호출할 수 있음을 말합니다. 

     

     

     

    댓글

Camel`s Tistory.