본문 바로가기
Java

자바 synchronized 이해

by 스르나 2021. 2. 24.

synchronized 메소드

public class Test {

    public synchronized void foo(){
        for(int i=0;i<5000;i++){
            System.out.println("foo call!");
        }
    }

    public synchronized void voo(){
        for(int i=0;i<5000;i++){
            System.out.println("voo call!");
        }
    }
}

위 코드를 보면 각 메소드에 synchronized 키워드를 붙인걸 볼 수 있다. 또한 각 메소드는 서로 공유하는 것이 없다.

 

그럼에도 synchronized를 저렇게 붙이면 두 메소드는 한 락에 모두 걸린다. 좀 더 정확하게는 메소드에 synchronized를 붙이면 해당 인스턴스의 lock을 가진 쓰레드가 인스턴스의 synchronized의 접근 권한을 모두 가져간다.

 

테스트는 다음과 같다.

 

public class Main {
    public static void main(String[] args) {
        Test test=new Test();
        new Thread(()->{
            test.foo();
        }).start();

        new Thread(()->{
            test.voo();
        }).start();
    }
}

위처럼 실행하면 첫번째 쓰레드가 작업(foo 메소드 실행)을 모두 마칠때까지 두번째 쓰레드는 voo를 호출하지 못한다.

 

static synchronized 메소드

그럼 여기서 한가지 생각이 든다 static 메소드는 어떻게 될까? static 메소드는 heap영역에 할당되지 않고 static영역에 들어간다. 그러므로 static synchronized 메소드는 인스턴스의 lock이 아닌 클래스의 lock을 가지게 된다..

 

 

public class Test {

    public static  void foo(){
        synchronized(Test.class){
            for(int i=0;i<10000;i++){
                System.out.println(Thread.currentThread().getName());
            }
        }
    }

    public  void voo(){
        synchronized(this){
            for(int i=0;i<10000;i++){
                System.out.println(Thread.currentThread().getName());
            }
        }
    }
}

위처럼하면 foo,voo는 별개의 lock객체를 가지게 된다.

 

public class Main {
    public static void main(String[] args) {
        
        new Thread(()->{
            Test.foo();
        }).start();

        new Thread(()->{
            new Test().voo();
        }).start();
    }
}

위처럼 실행하면 foo,voo가 번갈아 가며 실행되는 것을 알 수 있다.

 

synchronized block

synchronized block은 대부분의 설명에서 보다 정교한 locking을 위해 사용된다고 한다. 여기에 한가지 설명을 더 하면 synchronized block은 서로다른 lock 객체를 생성해서 더욱 다양한 동기화를 만드는데 의미가 있다.

 

public class Test {
    private final Integer num=0;

    public   void foo(){
        synchronized(num){
            for(int i=0;i<10000;i++){
                System.out.println(Thread.currentThread().getName());
            }
        }
    }

    public  void voo(){
        synchronized(this){
            for(int i=0;i<10000;i++){
                System.out.println(Thread.currentThread().getName());
            }
        }
    }
}

위의 예시를 보자 우선 foo는 num을 lock객체를 만드는데 사용하고 voo는 인스턴스를 이용한다.그렇기 때문에 현재 foo,voo는 쓰레드로 실행시키면 번갈아가며 실행된다.

 

public class Test {
    private final Integer num=0;

    public void foo(){
        synchronized(num){
            for(int i=0;i<10000;i++){
                System.out.println(Thread.currentThread().getName());
            }
        }
    }

    public  void voo(){
        synchronized(this){
            for(int i=0;i<10000;i++){
                System.out.println(Thread.currentThread().getName()+" "+num);
            }
        }
    }

    public void zoo(){
        synchronized(num){
            for(int i=0;i<10000;i++){
                System.out.println(Thread.currentThread().getName());
            }
        }
    }
}

그럼 위의 상황을 보자 foo는 num을 lock을 만드는데 사용하고 있다. 그리고 voo는 인스턴스를 그대로 이욯하고 있으나 한가지 더 for문 안에서 foo의 num을 이용한다.

그 다음으로운 zoo는 foo와 마찬가지로 num을 이용하고있다. 이때 실행 결과는 어떻게 될까?

 

결과는 foo,zoo 중에 우선 num의 lock을 가진쪽이 먼전 실행을 완료한다. 그리고 동시에 voo는 foo,zoo와 상관없이 실행된다.

 

여기서 알 수 있는 것은 synchronized block은 lock을 생성한 것이 겹치지 않는다면 서로 배체하지 않는 다는 것이다.(foo,zoo와 voo는 lock을 생성하는 객체가 달라서 서로 배체하지 않고 foo와zoo만 서로 배체한다.)

'Java' 카테고리의 다른 글

String,StringBuilder,StringBuffer  (0) 2021.03.10
Collection Framework 정리표  (0) 2021.02.25
JVM  (0) 2021.02.14
Iterator,Iterable  (0) 2021.02.13
Thread-3  (0) 2021.02.12