BACK/JAVA

[JAVA] 쓰레드의 동기화(메서드 동기화, 블록 동기화)

연듀 2022. 8. 10. 20:29

 

 

동기화란 하나의 작업이 완전히 완료된 후 다른 작업을 수행하는 것이다. 

멀티쓰레드를 사용할 땐 동기화가 필요하다. 

 

 

 

 

동기화를 사용하지 않았을 때 문제 발생 

// 공유 객체
class MyData{
    int data = 3;

    public void plusData(){
        int mydata=data; // 데이터 가져오기
        try {Thread.sleep(2000);} catch(InterruptedException e){}
        data=mydata+1; // 2초 후에 값을 1만큼 증가
    }
}

// 공유 객체를 사용하는 쓰레드
class PlusThread extends Thread{
    MyData myData;
    public PlusThread(MyData myData){ // 생성자의 매개변수로 MyData 객체를 입력받음
        this.myData=myData;
    }

    @Override
    public void run(){
        myData.plusData(); // 객체 내부의 plusData() 메서드 호출
        System.out.println(getName()+"실행 결과: "+myData.data);
    }
}

public class Main{
    public static void main(String[] args) {
        // 공유 객체 생성
        MyData myData = new MyData();

        // plusThread 1
        Thread plusThread1 = new PlusThread(myData);
        plusThread1.setName("plusThread1");
        plusThread1.start();

        try{Thread.sleep(1000);} catch(InterruptedException e) {} // 1초 기다림

        // plusThread2
        Thread plusThread2 = new PlusThread(myData);
        plusThread2.setName("plusThread2");
        plusThread2.start();
    }
}
plusThread1실행 결과: 4
plusThread2실행 결과: 4

 

 

두개의 쓰레드가 각각 MyData 객체의 data 필드 값을 1씩 증가시켰는데도 두 쓰레드 모두 4의 결괏값을 가진다.

이유는 두 번째 쓰레드가 data 필드를 증가시키는 시점에 아직 첫 번째 쓰레드의 실행이 끝나지 않았기 때문이다.

원하는 결괏값인 5가 나오게 하려면 하나의 쓰레드가 완전히 종료된 후 다른 쓰레드를 실행해야 한다.(동기화)

 

 

동기화 방법은 메서드 동기화와 블록 동기화로 나눌 수 있다.

 

 

 

 

메서드 동기화 

 

동기화하고자 하는 메서드의 리턴 타입 앞에 synchronized 키워드를 넣는다.

class MyData{
    int data = 3;

    public synchronized void plusData(){
        int mydata=data; // 데이터 가져오기
        try {Thread.sleep(2000);} catch(InterruptedException e){}
        data=mydata+1; // 2초 후에 값을 1만큼 증가
    }
}
plusThread1실행 결과: 4
plusThread2실행 결과: 5

 

 

블록 동기화 

 

메서드 전체 중에 동기화가 필요한 부분만 동기화 

class MyData{
    int data = 3;

    public void plusData(){
        synchronized (this){
            int mydata=data; // 데이터 가져오기
            try {Thread.sleep(2000);} catch(InterruptedException e){}
            data=mydata+1; // 2초 후에 값을 1만큼 증가
        }
    }
}
plusThread1실행 결과: 4
plusThread2실행 결과: 5

 

 

 

 

동기화의 원리

 

모든 객체는 자신만의 열쇠를 하나씩 갖고 있다. 

블록 동기화의 소괄호안에는 어떤 객체가 와도 무방하며 블록은 그 객체가 갖고 있는 열쇠로 잠긴다.

이 때, 메서드를 동기화하는 경우에는 this 객체의 열쇠만을 사용한다. 

 

 

 

3개의 동기화 영역이 동일한 열쇠로 동기화됐을  때 

class MyData{
    synchronized void abc(){ // this 객체가 갖고 있는 하나의 열쇠를 함께 사용 
            for(int i=0; i<3; i++){
                System.out.println(i+"sec");
                try{Thread.sleep(1000);} catch(InterruptedException e){}
            }

    }

    synchronized void bcd(){ // this 객체가 갖고 있는 하나의 열쇠를 함께 사용 
        for(int i=0; i<3; i++){
            System.out.println(i+"초");
            try{Thread.sleep(1000);} catch(InterruptedException e){}
        }
    }

    void cde(){ 
        synchronized (this){ // this 객체가 갖고 있는 하나의 열쇠를 함께 사용 
            for(int i=0; i<3; i++){
                System.out.println(i+"번째");
                try{Thread.sleep(1000);} catch(InterruptedException e){}
            }
        }
    }
}

public class Study {
    public static void main(String[] args) {
        MyData myData = new MyData(); // 공유 객체 

		// 3개의 쓰레드가 각각의 메서드 호출
        new Thread(){
            public void run(){
                myData.abc();
            };
        }.start();

        new Thread(){
            public void run(){
                myData.bcd();
            };
        }.start();

        new Thread(){
            public void run(){
                myData.cde();
            };
        }.start();

    }
}
0sec
1sec
2sec
0번째
1번째
2번째
0초
1초
2초

 

 

모두 this 객체의 열쇠를 사용하기 때문에 이들 중 1개의 메서드가 실행되는 도중에는 다른 어떤 메서드도 동시에 실행할 수 없다. 

 

 

 

 

 

 

동기화 메서드와 동기화 블록이 다른 열쇠를 사용할 때 

class MyData{
    synchronized void abc(){ // this 객체가 갖고 있는 하나의 열쇠를 함께 사용
            for(int i=0; i<3; i++){
                System.out.println(i+"sec");
                try{Thread.sleep(1000);} catch(InterruptedException e){}
            }

    }

    synchronized void bcd(){ // this 객체가 갖고 있는 하나의 열쇠를 함께 사용
        for(int i=0; i<3; i++){
            System.out.println(i+"초");
            try{Thread.sleep(1000);} catch(InterruptedException e){}
        }
    }

    void cde(){
        synchronized (new Object()){ // Object 객체가 갖고 있는 열쇠를 사용
            for(int i=0; i<3; i++){
                System.out.println(i+"번째");
                try{Thread.sleep(1000);} catch(InterruptedException e){}
            }
        }
    }
}

public class Study {
    public static void main(String[] args) {
        MyData myData = new MyData();

        new Thread(){
            public void run(){
                myData.abc();
            };
        }.start();

        new Thread(){
            public void run(){
                myData.bcd();
            };
        }.start();

        new Thread(){
            public void run(){
                myData.cde();
            };
        }.start();

    }
}
0번째
0sec
1sec
1번째
2번째
2sec
0초
1초
2초