Back-end/이것이 자바다[신용권 한빛미디어]

동기화 메소드와 동기화 블록

Ho's log 2021. 12. 4. 19:02

공유 객체를 사용할 때의 주의 할 점


싱글 스레드 프로그램에서는 한 개의 스레드가 객체를 독차지해서 사용하면 됨

멀티 스레드 프로그램에서는 스레드들이 객체를 공유해서 작업해야 하는 경우

이 경우, 스레드 A를 사용하던 객체가 스레드 B에 의해 상태가 변경 될 수 있기 때문에

스레드 A가 의도 했던 것과는 다른 결과를 산출 할 수도 있다.

 

이는 마치 여러 사람이 계산기를 함께 나눠 쓰는 상황과 같아서 사람 A가 계산기로

작업을 하다가 계산 결과를 메모리에 저장한 뒤 잠시 자리를 비웠을 때 

사람 B가 계산기를 만져서 앞 사람이 메모리에 저장한 값을 다른 값으로 변경하는 것과 같다

그런 다음 사람 A가 돌아와 계산기에 저장된 값을 이용해서 작업을 진행한다면 결국 사람 A는 

엉터리 값을 이용하게 된다 

 

 

User1 스레드가 Caluculator 객체의 memory 필드에 100을 먼저 저장하고 2초가 일시 정지 상태가 된다.

그 동안에 User2 스레드가 memory 필드 값을 50으로 변경 한다. 2초가 지나 User1 스레드가 다시 실행 상태가 되어

memory 필드의 값을 출력하면 User2 가 저장한 50이 나온다.

 

 

public class MainThreadExample {

	public static void main(String[] args){
    
    	Calculator calculator = new Calculator();
        
        User1 user1 = new User1();
        user1.setCalculator(calculator);
        user1.start();
        
        User2 user2 = new User2();
        user2.setCalulator(calculator);
        user2.start();
        
    
    }




}
public class Calculator{

	private int memory;
    
    public int getMemory(){
    
    	return memory;
    }
    
    public void setMemory(int memory){
    	this.memory = memory;
        
        try{
        	Thread.sleep(2000);
            
        
        } catch(InterruptedException e ){}
        System.out.println(Thread.currentThread().getName() + ": " + this.memory)
    
    
    }
}
public class User1 extends Thread {


	private Calculator calculator;
    
    public void setCaculator(Calculator calculator){
    
    	this.setName("User1");
        this.calculator = calculator;
    
    
    }
    
    public void run(){
    	calculaotor.setMemory(100);
    
    
    }





}
public class User2 extends Thread {


	private Calculator calculator;
    
    public void setCaculator(Calculator calculator){
    
    	this.setName("User2");
        this.calculator = calculator;
    
    
    }
    
    public void run(){
    	calculaotor.setMemory(50);
    
    
    }





}

 

 

 

동기화 메소드 및 동기화 블록


스레드 사용 중인 객체를 다른 스레드가 변경 할 수 없도록 스레드 작업이 끝날 때 까지

겍체에 잠금을 걸어서 다른 스레드가 사용할 수 없도록 해야 한다. 

멀티 스레드 프로그램에서 단 하나의 스레드만을 실행할 수 있는 코드 영역을 임계 영역(critical section)

 

자바는 임계 영역을 지정하기 위해 동기화(synchronized) 메소드와 동기화 블록을 제공 한다.

스레드가 객체 내부의 동기화메소드 또는 블록에 들어가면 즉시 객체에 잠금을 걸어 다른 스레드가 임계 영역 코드를 실행하지 못하도록 한다.

동기화 메소드를 만드는 방법은 다음과 같이 메소드 선언에 synchronized 키워드를 붙이면 된다. 

synchronized 키워드는 인스턴스와 정적 메소드 어디든 붙일 수 있다.

public synchronized void method(){
	임계 영역 //단 하나의 스레드만 실행 
    

}

 

 

동기화 메소드는 메소드 전체 내용이 임계 영역이므로

스레드가 동기화 메소드를 실행하는 즉시 객체에는 잠금이 일어나고, 

스레드가 동기화 메소드를 실행 종료 하면 잠금이 풀린다. 

 

메소드 전체 내용이 아니라, 일부 내용만 임계 영역으로 만들고 싶다면

다음과 같이 동기화(synchronized) 블록을 만들면 된다. 

 

public void method(){
	//여러 스레드가 실행 가능 영역
    ..
    
    synchronized(공유객체){
    
    	임계 영역 // 단 하나의 스레드만 실행 
    
    }
    //여러 스레드 실행 가능 영역 
    ...

}

 

동기화 블록의 외부 코드들은 여러 스레드가 동시에 실행할 수 있지만,

동기화 블록의 내부 코드는 임계 영역이므로 한 번에 한 스레드만 실행할 수 있고 다른 스레드는 실행 할 수 없다.

만약 동기화 메소드와 동기화 블록이 여러 개 있을 경우, 

스레드가 이들 중 하나를 실행 할 때

다른 스레드는 해당 메소드는 물론이고 다른 동기화 메소드 및 블록도 실행할 수 없다.

하지만 일반 메소드는 실행이 가능하다.

 

 

 

이전 예제 Calculator 수정 

setMemory() 메소드를 동기화 메소드로 만들어

User1 스레드가 setMemory()를 실행할 동안 

User2 스레드가 setMemory() 메소드를 실행할 수 없도록 했다.

 

public class Calculator {

	private int memory;
    
    public int getMemory(){
    	return memory;
    }
    
    public synchronized void setMemory(int memory){
    
    	this.memory = memory;
        
        try {
        	Thread.sleep(2000);
        } catch (InterruptedException e){}
    	System.out.println(Thread.currentThread().getNAme() + ": " + this.memory)
    }






}

 

 

User1 스레드는 Calculator 객체의 동기화 메소드인 setMemory()를 실행하는 순간 Calculator 객체를 잠근다.

메인 스레드가 User2 스레드를 실행시키지만, 동기화 메소드인 setMemeory() 를 실행시키지는 못하고 User1이

setMemory()를 모두 실행 할 동안 대기해야 한다.

User1 스레드가 setMemory() 메소드를 모두 실행하고 나면 

User2 스레드가 setMemory() 메소드를 실행한다.

결국 User1 스레드가 Calculator 객체를 사용할 동안 User2 스레드는 Calculator 객체를 사용하지 못하므로

User1 스레드는 안전하게 방해받지 않고 Calculator 객체를 사용할 수 있게 된다

위 예제에서는 Calculator 객체의 setMemory() 메소드를 동기화 메소드로 만들었는데 

 

다음과 같이 동기화 블로으로도 만들 수 있다.

public void setMemory(int memory){
	synchronized (this){
    	this.memory = memory;
        
        try {
        
        	Thread.sleep(2000);
            
        } catch(InterruptedException e){}
        System.out.println(Thread.currrentThread().getName() + ": " + this.memory + " 저장")
    
    
    }




}

 

스레드가 동기화 블록으로 들어 가면 this(Calculator 객체)를 잠그고, 동기화 블록을 실행한다. 

동기화 블록을 모두 실행 할 때 까지 다른 스레드들은 this(Calculator 객체)의

모든 동기화 메소드 또는 동기화 블록을 실행 할 수 없게 된다. 

 

'Back-end > 이것이 자바다[신용권 한빛미디어]' 카테고리의 다른 글

스레드 상태 제어  (0) 2021.12.05
스레드 상태  (0) 2021.12.04
스레드 우선순위  (0) 2021.11.27
작업 스레드 생성과 실행  (0) 2021.11.24
멀티 스레드 개념  (0) 2021.11.22