본문 바로가기
Java/Java 기초문법

[JAVA 기초] 쓰레드(Thread) 3 - 동기화(Synchronized)와 데몬쓰레드(Daemon Thread)

by dev수니 2021. 4. 13.
반응형

 

 

 1  동기화(Synchronized)

 

두 쓰레드가 같은 프로세스 내의 자원을 공유하기 때무네 서로의 작업에 영향을 준다. 예를 들어 통장에 10만원이 있고 다른 두 ATM기기에서 돈을 꺼내려 한다. 두 ATM 기기에서 만약 6만원 6만원씩 꺼낼 수도 있기 때문에 동시에 출금이 가능하게 해서는 안된다. 따라서 한 ATM 기기를 사용할때 락을 걸어 다른 ATM에서 접근하지 못하도록 해야할 것이다. 

 

이처럼 한 쓰레드에서 사용하는 자원을 다른 쓰레드가 간섭하지 못하도록 락(rock)을 거는 것을 쓰레드의 동기화라고 한다.

 

여러개의 메서드들이 공유 객체를 사용할 때 동기화를 시켜줬을 경우 0.000001초라도 메서드가 먼저 실행되면 해당 객체에 사용권을 얻게된다. 이 사용권모니터링 락이라고 부른다. 이렇게 되면 해당 쓰레드가 모두 실행되고 나서 다음 쓰레드가 실행된다.

 

 

synchronized 를 사용하는 방법은 두가지가 있다.

1. 동기화 메서드 방식
2. 동기화 블럭 방식

 

이다. 동기화 메서드 방식은 다음과 같다. ( 리턴타입 앞synchronized )

public sychronized void 메서드명 () {}

 

다음은 동기화 블럭 방식이다.

메서드 ( ) {
  sychronized (참조값) { }
}

참조값에는 this, 해당 메서드명 등이 들어갈 수 있다.

 

 

 

 

Ex

아래 예제는 멀티쓰레드의 예제이다. 해당 예제의 구조 파악 후 synchronized 를 적용해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package Thread;
 
class Symbols {
    public void symbolsA() {
        for (int i = 0; i < 10; i++) {
            System.out.println("***");
            try {
                Thread.sleep((int)(Math.random()*1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public void symbolsB() {
        for (int i = 0; i < 10; i++) {
            System.out.println("---");
            try {
                Thread.sleep((int)(Math.random()*1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public void symbolsC() {
        for (int i = 0; i < 10; i++) {
            System.out.println("!!!");
            try {
                Thread.sleep((int)(Math.random()*1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class PrintSymbols extends Thread {    // 쓰레드 객체 
    int type;        // 저장할 타입을 선언
    Symbols symbols;    // Symbols타입 변수 symbols를 선언
    
    PrintSymbols(int type, Symbols symbols) {    // 생성자를 통해 Symbols 객체를 담음.
        this.type = type;
        this.symbols = symbols;
    }
    public void run() {
        switch (type) {
        case 1:
            symbols.symbolsA();
            break;
        case 2:
            symbols.symbolsB();
            break;
        case 3:
            symbols.symbolsC();
            break;
        }
    }
}
public class MultiThreadSymbols {
    public static void main(String[] args) {
        Symbols sym = new Symbols();
        PrintSymbols star = new PrintSymbols(1, sym);    // Symbols객체의 참조변수를 매개변수로 넣어줌.
        PrintSymbols minu = new PrintSymbols(2, sym);
        PrintSymbols bang = new PrintSymbols(3, sym);
        star.start();
        minu.start();
        bang.start();
    }
}
cs

위와 같이 Symbols 객체를 생성하고 그 안에 각각 다른 특수문자의 A 메소드 , B 메소드, C메소드를 생성하였다.

 

그리고 이 객체를 쓰레드화 시킬 클래스 PrintSymbols 를 생성하여 줄36에서 각 메서드를 저장해줄 타입을 선언해주고 Symbols 클래스타입의 변수 symbols 를 선언해주었다.

 

그리고 생성자를 통해 type과 symbols 를 매개변수로 입력받게 하였고 해당 매개변수들을 this 를 이용해 줄36,37에 저장하게 해주었다.

 

그리고 run()메서드 오버라이딩 하여 switch 문으로 위와 같이 실행되도록 생성해주었다.

 

그 후 main() 에서 Symbols 객체를 생성하였고

PrintSymbols 의 객체들을 생성하면서 타입 매개변수로 1,2,3을 넣어주었고 Symbols 객체의 참조변수 sym 을 symbols의 매개변수로 담아주었다.

 

그리고 나서 start()를 통해 호출하면 다음과 같이 출력된다.

 

 

그리고 위의 예제를 synchronized 로 고친 것이다. 고친 부분만 잘라서 가져왔다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Symbols {
    public synchronized void symbolsA() {
        for (int i = 0; i < 10; i++) {
            System.out.println("***");
            try {
                Thread.sleep((int)(Math.random()*1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public synchronized void symbolsB() {
        for (int i = 0; i < 10; i++) {
            System.out.println("---");
            try {
                Thread.sleep((int)(Math.random()*1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public void symbolsC() {
        for (int i = 0; i < 10; i++) {
            synchronized (this) {
                System.out.println("!!!");
            }
            try {
                Thread.sleep((int)(Math.random()*1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
cs

줄2의 symbolsA() 메서드와 줄12의 symbolsB() 메서드에 동기화 메서드 방식을 적용하여 동기화 하였으며

줄22의 symbolsC() 메서드는 실제 출력하는 부분인 줄25 부분만 동기화 블록 방식을 사용하여 동기화 시켜주었다.

 

따라서 다음과 같이 메서드 symbolsA 와 symbolsB 는 순차적으로 출력될 것이고,

symbolsC 메서드는 한줄이 먼저 출력 된 이후 모니터링 락이 다른 메서드에게로 넘어가게 된다. 따라서 다음과 같이 출력된다.

 

 

 

 

 

 

 

 

 2  데몬쓰레드 (Daemon Thread)

 

일반쓰레드의 작업을 돕는 보조역할을 하는 쓰레드이다. 따라서 일반 쓰레드가 종료되면 자동으로 종료된다.

 

void setDaemon(boolean on) : 쓰레드를 데몬 쓰레드로 또는 사용자 쓰레드로 변경할 수 있다. 매개변수 on을 true로 지정하면 데몬쓰레드가 된다.

* setDaemon 은 반드시 start()호출 전에 실행되야한다. 그렇지 않으면 IllegalThreadException 이 발생한다.

 

 

다음과 같이 데몬쓰레드를 작성할 수 있다.

public void run(){
 while(true){
    try{
      sleep // n초마다반복
    }catch(){}
   if(autoSave){
      autosave();
   }
}
autosave(){
   //실행할 구문
}

 

 

 

 

Ex

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package Thread;
 
class Count extends Thread {
    public void run(){
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(i+1);
        }
    }
}
class Daemon extends Thread {
    public void run() {
        while(true) {
            try {
                Thread.sleep(1000*5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        if(true)
            autoSave();
        }
    }
    void autoSave(){
        System.out.println("프로그램 자동 저장.");
    }
}
public class Daemon_Thread extends Thread{
    public static void main(String[] args) {
    // main 쓰레드
        Count th = new Count();
        Daemon d = new Daemon();
        d.setDaemon(true);
        th.start();
        d.start();
    }
}
cs

위와 같이 Count 쓰레드를 작성해주고 Daemon클래스를 작성해주었다.

 

Count 쓰레드는 10번 반복하여 1~10 까지 1초마다 반복하고,

Daemon 클래스는 while문을 사용해 무한반복으로 5초 시간제한을 걸어놓았다. 그리고 if문을 사용해 true일 경우 autosave() 메서드를 호출하여 줄27의 자동저장을 출력하도록 설정하였다.

 

따라서 main에서 CountDaemon 객체를 생성하여 Daemon 객체는 setDaemon(true) 로 데몬쓰레드로 지정하고 줄37,28에서 두 객체 다 start()를 호출해 run 메서드가 실행되도록 하였다.

 

따라서 위의 결과와 같이 Count 쓰레드가 종료됐을 때 데몬쓰레드도 자동 종료된다.

 

 

 

 

 

 

 

반응형

댓글