안녕하세요.

이번에는 오랫만에 삽질한(맨날 삽질하면서 무슨...) 정렬에 대해서 포스팅을 해보려고 합니다.

 

기본적인건데 맨날 삽질하는 것도 있고, 까먹는 것도 있고 해서 겸사겸사 기록용으로 올릴겸 포스팅을 해봅니다.

 

이번 포스팅에서 다룰 주제는 다음과 같습니다.

 

1. List<String> 과 같은 일반적인 부분이 아닌 List<ClassName> ex) List<TestDate> 형식의 객체를 요소로 가지고 있는 List의 정렬에 대하여 알아보겠습니다.

2. Date 타입의 필드를 기준으로 정렬하는 방법에 대하여 알아보도록 하겠습니다.

3. Collections / Comparator / Collectors 로 정렬 하는 방법에 대하여 알아보도록 하겠습니다.

 

* 위에서 말씀드렸지만 이번에 다루게 될 정렬 Data의 타입은 Date 타입입니다.

  솔직히 Date 타입은 어떻게 정렬해야되나 생각하면서 Date -> String 으로 바꿔보기도하고 별짓을 다했었는데

  의미가 없더라구요. (그건 제가 못해서...)

 

우선 테스트를 위한 TestDate 클래스를 작성하도록 하겠습니다.

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
import java.util.Date;
 
public class TestDate {
    private String userId;
    private String userName;
    private Date register_dt;
    private String tmpField1;
    
    public TestDate(String userId, String userName) {
        this.userId = userId;
        this.userName = userName;
    }
    
    public String getUserId() {
        return this.userId;
    }
    
    public String getUserName() {
        return this.userName;
    }
    
    public Date getRegister_Dt() {
        return this.register_dt;
    }
    
    public String getTmpField1() {
        return this.tmpField1;
    }
    
    public void setUserId(String userId) {
        this.userId = userId;
    }
    
    public void setUsername(String userName) {
        this.userName = userName;
    }
    
    public void setRegister_Dt(Date register_dt) {
        this.register_dt = register_dt;
    }
    
    public void setTmpField1(String tmpField1) {
        this.tmpField1 = tmpField1;
    }
}
cs

 

 

작성한 TestDate 클래스를 활용하여 정렬을 해보겠습니다.

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import java.util.Collections;
import java.util.Comparator;
import java.util.Arrays;
import java.text.SimpleDateFormat;
import java.text.DateFormat;
 
public class DateTestMain {
    public static void main(String[] args) {
        // 테스트를 위한 초기값 설정
        DateFormat df = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss.SSSS");
        
        TestDate td1 = new TestDate("td1""td1");
        TestDate td2 = new TestDate("td2""td2");
        TestDate td3 = new TestDate("td3""td3");
        TestDate td4 = new TestDate("td4""td4");
        TestDate td5 = new TestDate("td5""td5");
        TestDate td6 = new TestDate("td6""td6");
        TestDate td7 = new TestDate("td7""td7");
        TestDate td8 = new TestDate("td8""td8");
        TestDate td9 = new TestDate("td9""td9");
        TestDate td10 = new TestDate("td10""td10");
 
        // Date 값의 비교를 위해 for문을 100만 번 씩 반복 하였습니다.
        if(td1.getRegister_Dt()==null)    td1.setRegister_Dt(new Date());
        if(td1.getTmpField1()==null || "".equals(td1.getTmpField1())) td1.setTmpField1("080");
        for(int i=0; i<1000000; i++) {}
        if(td2.getRegister_Dt()==null)    td2.setRegister_Dt(new Date());
        if(td2.getTmpField1()==null || "".equals(td2.getTmpField1())) td2.setTmpField1("075");
        for(int i=0; i<1000000; i++) {}
        if(td3.getRegister_Dt()==null)    td3.setRegister_Dt(new Date());
        if(td3.getTmpField1()==null || "".equals(td3.getTmpField1())) td3.setTmpField1("070");
        for(int i=0; i<1000000; i++) {}
        if(td4.getRegister_Dt()==null)    td4.setRegister_Dt(new Date());
        if(td4.getTmpField1()==null || "".equals(td4.getTmpField1())) td4.setTmpField1("100");
        for(int i=0; i<1000000; i++) {}
        if(td5.getRegister_Dt()==null)    td5.setRegister_Dt(new Date());
        if(td5.getTmpField1()==null || "".equals(td5.getTmpField1())) td5.setTmpField1("005");
        for(int i=0; i<1000000; i++) {}
        if(td6.getRegister_Dt()==null)    td6.setRegister_Dt(new Date());
        if(td6.getTmpField1()==null || "".equals(td6.getTmpField1())) td6.setTmpField1("010");
        for(int i=0; i<1000000; i++) {}
        if(td7.getRegister_Dt()==null)    td7.setRegister_Dt(new Date());
        if(td7.getTmpField1()==null || "".equals(td7.getTmpField1())) td7.setTmpField1("020");
        for(int i=0; i<1000000; i++) {}
        if(td8.getRegister_Dt()==null)    td8.setRegister_Dt(new Date());
        if(td8.getTmpField1()==null || "".equals(td8.getTmpField1())) td8.setTmpField1("100");
        for(int i=0; i<1000000; i++) {}
        if(td9.getRegister_Dt()==null)    td9.setRegister_Dt(new Date());
        if(td9.getTmpField1()==null || "".equals(td9.getTmpField1())) td9.setTmpField1("005");
        for(int i=0; i<1000000; i++) {}
        if(td10.getRegister_Dt()==null)    td10.setRegister_Dt(new Date());
        if(td10.getTmpField1()==null || "".equals(td10.getTmpField1())) td10.setTmpField1("010");
 
        List<TestDate> tdList = Arrays.asList(td2, td1, td3, td10, td5, td8, td9, td4, td6, td7); // 테스트용 List
 
        for(TestDate td : tdList) // List에 값이 어떤식으로 저장되어 있는지 확인 하기 위해 출력
            System.out.println("userName : " + td.getUserName() + " / reg_dt : " + df.format(td.getRegister_Dt()));
 
        // 출력 결과
        /*
         * userName : td2 / reg_dt : 2020.08.25 09:08:35.0816 
         * userName : td1 / reg_dt : 2020.08.25 09:08:35.0809
         * userName : td3 / reg_dt : 2020.08.25 09:08:35.0818
         * userName : td10 / reg_dt : 2020.08.25 09:08:35.0830
         * userName : td5 / reg_dt : 2020.08.25 09:08:35.0821
         * userName : td8 / reg_dt : 2020.08.25 09:08:35.0826
         * userName : td9 / reg_dt : 2020.08.25 09:08:35.0828
         * userName : td4 / reg_dt : 2020.08.25 09:08:35.0820
         * userName : td6 / reg_dt : 2020.08.25 09:08:35.0823
         * userName : td7 / reg_dt : 2020.08.25 09:08:35.0825
         */
 
        //=============== 정렬 시작 ===============
        /*
         * Option 1
         * Collections.sort() with Comparator
         */
        Collections.sort(tdList, new Comparator<TestDate>() { // 익명객체 사용
            @Override
            public int compare(TestDate arg1, TestDate arg2) { // Sort Order By asc
                return arg1.getRegister_Dt().compareTo(arg2.getRegister_Dt()); // arg1 - 기준 값, arg2 - 비교 값
            }
        });
 
        Collections.sort(tdList, new Comparator<TestDate>() { // 익명객체 사용
            @Override
            public int compare(TestDate arg1, TestDate arg2) { // Sort Order By desc
                return arg2.getRegister_Dt().compareTo(arg1.getRegister_Dt()); // arg2 - 기준 값, arg1 - 비교 값
            }
        });
 
        /*
         * Option 2
         * List interface sort() [Java 8]
         */
        tdList.sort(Comparator.comparing(TestDate::getRegister_Dt)); // Sort Order By asc - Comparator의 comparing 사용, ::를 활용한 참조 방식 사용
        tdList.sort(Comparator.comparing(TestDate::getRegister_Dt).reversed()); // Sort Order By desc - Comparator의 comparing 사용, ::를 활용한 참조 방식 사용, reversed 사용시 내림차순
 
        /*
         * Option 3
         * Stream interface sorted() [Java 8]
         */
        List<TestDate> ascTD = tdList.stream() // Sort Order By asc - Comparator의 comparing 사용, ::를 활용한 참조 방식 사용, stream을 활용한 List의 sorted사용 및 collect를 활용한 Collectors.toList() 사용
                .sorted(Comparator.comparing(TestDate::getRegister_Dt))
                .collect(Collectors.toList());
 
        List<TestDate> descTD = tdList.stream() // Sort Order By desc - Comparator의 comparing 사용, ::를 활용한 참조 방식 사용, stream을 활용한 List의 sorted사용 및 collect를 활용한 Collectors.toList() 사용, reversed 사용시 내림차순
                .sorted(Comparator.comparing(TestDate::getRegister_Dt).reversed())
                .collect(Collectors.toList());
 
        //=============== 정렬 종료 ===============
 
        /*
         * 위 Option 1~3 까지의 방식 중 하나를 사용하셔서 정렬을 하신 후 출력을 해보시면 다음과 같은 결과를 확인하실 수 있습니다.
         * 여기서는 desc (내림차순) 방식으로 정렬 및 테스트를 진행 하였습니다.
         */
 
        for(TestDate td : tdList)
            System.out.println("userName : " + td.getUserName() + " / reg_dt : " + df.format(td.getRegister_Dt()));
 
        // 출력 결과
        /*
         * userName : td10 / reg_dt : 2020.08.25 09:08:35.0830
         * userName : td9 / reg_dt : 2020.08.25 09:08:35.0828
         * userName : td8 / reg_dt : 2020.08.25 09:08:35.0826
         * userName : td7 / reg_dt : 2020.08.25 09:08:35.0825
         * userName : td6 / reg_dt : 2020.08.25 09:08:35.0823
         * userName : td5 / reg_dt : 2020.08.25 09:08:35.0821
         * userName : td4 / reg_dt : 2020.08.25 09:08:35.0820
         * userName : td3 / reg_dt : 2020.08.25 09:08:35.0818
         * userName : td2 / reg_dt : 2020.08.25 09:08:35.0816
         * userName : td1 / reg_dt : 2020.08.25 09:08:35.0809
         */
 
        // 추가로 날짜 정렬 이후, 우선 순위에 따라 정렬을 해보고 결과를 확인해 보겠습니다. (desc-내림차순 정렬 사용)
 
        tdList.sort(Comparator.comparing(TestDate::getTmpField1).reversed()); // 소스코드가 짧아서 이렇게 썼습니다. (사실은 귀찮아서...)
 
        for(TestDate td : tdList)
            System.out.println("userName : " + td.getUserName() + " / tmpField : " + td.getTmpField1());
 
        // 출력 결과
        /*
         * userName : td8 / tmpField : 100
         * userName : td4 / tmpField : 100
         * userName : td1 / tmpField : 080
         * userName : td2 / tmpField : 075
         * userName : td3 / tmpField : 070
         * userName : td7 / tmpField : 020
         * userName : td10 / tmpField : 010
         * userName : td6 / tmpField : 010
         * userName : td9 / tmpField : 005
         * userName : td5 / tmpField : 005
         */
    }
}
cs

 

결론부터 말씀드리면

 

포스팅의 목적이었던

 

- Date 타입의 필드만으로 정렬하기

- Date 타입으로 정렬 후, 우선 순위에 따라 정렬하기

 

위 2가지가 정상적으로 작동하는 것을 확인 하실 수 있습니다.

 

1. compare 오버라이딩

2. compareTo로 비교

3. Date 타입 -> String 타입 x / Date 타입 자체만으로 compareTo로 비교

4. 3까지의 과정 이후 우선순위에 따라 정렬하기

 

이렇게 하면 원하는 방식으로 정렬을 하실 수 있습니다.

 

위의 정렬 소스코드에서 Option 1을 제외한 Option 2, 3의 경우 Java 8 이라고 명시해 놓았는데,

Java 8 버전 이후부터 Option 2, 3 방식이 지원되기에 가능한 방식어서 명시해 놓았습니다.

* Option 2의 List.sort()는 Java 8 버전이 아니어도 지원이 됩니다.

  :: 참조 방식에 대한 지원이 Java 8 버전 이후부터 지원이 되므로 주의 하시기 바랍니다.

 

여기서는 소스코드에 대해서 자세하게 설명하지 않겠습니다.

 

핵심인 Date 타입 자체만으로도 비교가 가능하다는 것을 알고, 정렬 방식의 종류버전에 따른 차이가 있다는 점을 마지막으로 포스팅을 마치도록 하겠습니다.

 

 

Java 스레드 풀에 대하여 글을 올리고 싶었으나...

이번 주말 중으로 올릴 수 있으면 올리도록 하겠습니다.

못 올릴 수도 있습니다!

 

올리기는 반드시 올릴거에요!

 

결국 주말에 올리지 못 했습니다....

(지금 하고 있는게 굉장히 바빠서..)

 

이번 달 안에 올릴 수 있을지 모르겠네요 ...

아 물론! 스레드 풀은 당연히 공부가 끝난 상태입니다.

 

올리긴 올리겠습니다..

 

 

이번에는 스레드 그룹에 대하여 알아 보도록 하겠습니다.

 

스레드 그룹의 정의는 다음과 같이 되어 있습니다.

 

- 스레드 그룹(ThreadGroup)은 관련된 스레드를 묶어서 관리할 목적으로 이용된다.

 

추가적인 설명은 다음과 같습니다.

 

JVM이 실행되면 system 스레드 그룹을 만들고, JVM 운영에 필요한 스레드들을 생성해서 system 스레드 그룹에 포함시킵니다.

system의 하위 스레드 그룹으로 main을 만들고 메인 스레드를 main 스레드 그룹에 포함시킵니다.

스레드는 반드시 하나의 스레드 그룹에 포함됩니다.

명시적으로 스레드 그룹에 포함시키지 않으면 기본적으로 자신을 생성한 스레드와 같은 스레드 그룹에 속하게 됩니다.

우리가 생성하는 작업 스레드는 대부분 main 스레드가 생성하므로 기본적으로 main 스레드가 생성하므로 기본적으로 main 스레드 그룹에 속하게 됩니다.

 

※ 추가 설명에서 main 스레드 또한 사용자가 직접 생성하기 때문에 유저 스레드라고 생각합니다.

ex) public static void main(String[] args) {}

 


스레드를 그룹 지정하여 관리하게 되면 여러가지 편할 것 같습니다(현업에서 많이 사용할지는 아직 잘 모르겠습니다.)

 

스레드 그룹이 있을 경우 관리를 위해 그룹 이름을 알아야 하지요!

그에 따른 스레드 그룹 이름 얻기.

 

스레드 그룹을 만들지도 않았는데 관리는 무슨!

스레드 그룹 생성.

 

다 사용 했으면 자원 돌려줘야지!

스레드 그룹의 일괄 interrupt().

 

이렇게 3가지에 대하여 알아 보도록 하겠습니다.

 


스레드 그룹의 이름을 얻기 위해서는 getName() 메소드를 이용하여 확인할 수 있습니다.

 

 

다음 예제를 통하여 알아 보도록 하겠습니다.

 

 

 

 

ThreadInfoExample.java - 현재 실행 중인 스레드 정보

package thread_group;
 
 
public class ThreadInfoExample {
 
    public static void main(String[] args) {
        AutoSaveThread autoSaveThread = new AutoSaveThread();
        autoSaveThread.setName("AutoSaveThread");
        autoSaveThread.setDaemon(true);
        autoSaveThread.start();
        
        Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
        Set<Thread> threads = map.keySet();
        for(Thread thread : threads) {
            System.out.println("Name: " + thread.getName() +
                    ((thread.isDaemon())?"(데몬)""(주)"));
            System.out.println("\t" + "소속그룹: " + thread.getThreadGroup().getName());
            System.out.println();
        }
    }
}

※ 이 코드에서 AutoSaveThread 클래스는 제 이전 글인 데몬 스레드 글에서 확인 하실 수 있습니다.

    또는 이것이 자바다 교재를 확인하시거나 이것이 자바다 교재에서 제공하는 예제코드를 활용하시기 바랍니다.

 

 

이 코드를 실행하시게 되면 다음과 같은 결과를 확인할 수 있습니다.

 

Name: AutoSaveThread(데몬)

소속그룹: main

 

Name: Reference Handler(데몬)

소속그룹: system

 

Name: Signal Dispatcher(데몬)

소속그룹: system

 

Name: main(주)

소속그룹: main

 

Name: Finalizer(데몬)

소속그룹: system

 


스레드 그룹 생성은 간단하게 생성 방법에 대해서만 알아보고, 스레드 그룹의 일괄 interrupt()에서 사용하며 익숙해지도록 하겠습니다.

 

스레드 그룹 생성은 다음과 같이 할 수 있습니다.

 


ThreadGroup tg = new ThreadGroup(String name);

ThreadGroup tg = new ThreadGroup(ThreadGroup parent, String name);


Thread t = new Thread(ThreadGroup group, Runnable target);

Thread t = new Thread(ThreadGroup group, Runnable target, String name);

Thread t = new Thread(ThreadGroup group, Runnable target, String name, long stackSize);

Thread t = new Thread(ThreadGroup group, String name);


이 6가지를 통해서 알 수 있는 것은 다음과 같습니다.

 

- 스레드 그룹 생성 시 부모(parent) 스레드 그룹을 지정하지 않으면 현재 스레드가 속한 그룹의 하위 그릅으로 생성됩니다.

ex) main 스레드가 ThreadGroup(String name)을 이용해 새로운 스레드 그룹 생성시, main 스레드 그룹의 하위 스레드 그룹이 됩니다.

- Runnable 타입의 target은 Runnable 구현 객체를 말합니다.

- String 타입의 name은 스레드의 이름입니다.

- long 타입의 stackSize는 JVM이 이 스레드에 할당할 stack 크기입니다.

 


스레드 그룹의 일괄 interrupt()

 

스레드 그룹을 생성하게 될 경우의 편리성! 이라 할 수 있는게 대표적으로 스레드 종료가 있습니다.

스레드 그룹을 지정하지 않고, interrupt() 하게 될 경우, 스레드마다 interrupt()를 작성해 줘야 합니다.

하지만! 스레드 그룹을 지정해서 관리할 경우, 같은 스레드 그룹에 소속되어 있을 경우 스레드 그룹의 interrupt() 메소드를 한 번만 호출해주면 됩니다.

interrupt()를 작성하지도 않았는데 이것이 가능한 이유는 스레드 그룹의 interrupt() 메소드는 포함된 모든 스레드의 interrupt() 메소드를 내부적으로 호출해주기 때문입니다.

 

글이 아닌 [그림]으로 조금 더 직관적으로 알아 보도록 하겠습니다.

 

[그림 - 스레드 그룹의 일괄 interrupt()]

또한 다음과 같은 점도 있습니다.

 

스레드 그룹의 interrupt() 메소드는 소속된 스레드의 interrupt() 메소드를 호출만 할 뿐 개별 스레드에서 발생하는 InterruptedException에 대한 예외 처리를 하지 않습니다. 그렇기 때문에 안전한 종료를 위해서는 개별 스레드에 대해서 예외 처리를 해야 합니다.

 

스레드 그룹에도 interrupt() 메소드 이외에 suspend(), resume(), stop() 메소드들이 있는데, 모두 Deprecated 되었습니다.

 

다음 표는 Thread Group이 가지고 있는 주요 메소드들입니다.

 

메소드 설명
int activeCount() 현재 그룹 및 하위 그룹에서 활동 중인 모든 스레드의 수를 리턴한다.
int activeGroupCount() 현재 그룹에서 활동 중인 모든 하위 그룹의 수를 리턴한다.
void checkAccess() 현재 스레드가 스레드 그룹을 변경할 권한이 있는지 체크한다. 만약 권한이 없으면 SecurityException을 발생시킨다.
void destroy() 현재 그룹 및 하위 그룹을 모두 삭제한다. 단, 그룹 내에 포함된 모든 스레드들이 종료 상태가 되어야 한다.
boolean isDestroyed() 현재 그룹이 삭제되었는지 여부를 리턴한다.
int getMaxPriority() 현재 그룹에 포함된 스레드가 가질 수 있는 최대 우선순위를 리턴한다.
void setMaxPriority(int pri) 현재 그룹에 포함된 스레드가 가질 수 있는 최대 우선순위를 설정한다.
String getName() 현재 그룹의 이름을 리턴한다.
ThreadGroup getParent() 현재 그룹의 부모 그룹을 리턴한다.
boolean parentOf(ThreadGroup g) 현재 그룹이 매개값으로 지정한 스레드 그룹의 부모인지 여부를 리턴한다.
boolean isDaemon() 현재 그룹이 데몬 그룹인지 여부를 리턴한다.
void setDaemon(boolean daemon) 현재 그룹을 데몬 그룹으로 설정한다.
void list() 현재 그룹에 포함된 스레드와 하위 그룹에 대한 정보를 출력한다.
void interrupt() 현재 그룹에 포함된 모든 스레드들을 interrupt한다.

 

많습니다...

다 외우시거나 외우고 계시는 분들이 많을 것 같습니다만, 저는 필요할 때마다 찾아서 사용하고 많이 사용하면서 익숙해지도록 해야겠네요.

 

이제 예제를 통하여 스레드 그룹의 interrupt()를 해보도록 하겠습니다.

다음의 예제는 스레드 그룹 생성 후, 정보를 출력합니다. 마지막으로 3초 후 스레드 그룹의 interrupt() 메소드를 호출해서 스레드 그룹에 포함된 모든 스레드들을 종료시킵니다.

 

 

 

 

WorkThread.java - InterruptedException이 발생할 때 스레드가 종료되도록 함

package thread_group_create;
 
public class WorkThread extends Thread {
 
    public WorkThread(ThreadGroup threadGroup, String threadName) {
        super(threadGroup, threadName);    // 스레드 그룹과 스레드 이름을 설정
    }
    
    @Override
    public void run() {
        while(true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                /*
                    InterruptedException이 발생될 때,
                    while문을 빠져나와 스레드를 종료시킵니다.
                */
                System.out.println(getName() + " interrupted");
                break;
            }
        }
        System.out.println(getName() + " 종료됨");
    }
}

 

 

 

 

ThreadGroupExample.java - 스레드 그룹을 이용한 일괄 종료 예제

package thread_group_create;
 
public class ThreadGroupExample {
 
    public static void main(String[] args) {
        //    myGroup에 두 스레드를 포함시킵니다.
        ThreadGroup myGroup = new ThreadGroup("myGroup");
        WorkThread workThreadA = new WorkThread(myGroup, "workThreadA");
        WorkThread workThreadB = new WorkThread(myGroup, "workThreadB");
        
        workThreadA.start();
        workThreadB.start();
        
        System.out.println("[ main 스레드 그룹의 list() 메소드 출력 내용 ]");
        ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
        mainGroup.list();
        System.out.println();
        
        try { Thread.sleep(3000); } catch (InterruptedException e) {}
        
        System.out.println("[ myGroup 스레드 그룹의 interrupt() 메소드 호출 ]");
        myGroup.interrupt();
    }
}

 

 

이 예제를 실행할 경우 다음과 같은 결과를 확인할 수 있습니다.

 

[ main 스레드 그룹의 list() 메소드 출력 내용 ]

java.lang.ThreadGroup[name=main,maxpri=10]

    Thread[main,5,main]

    java.lang.ThreadGroup[name=myGroup,maxpri=10]

        Thread[workThreadA,5,myGroup]

        Thread[workThreadB,5,myGroup]

 

[ myGroup 스레드 그룹의 interrupt() 메소드 호출 ]

workThreadB interrupted

workThreadB 종료됨

workThreadA interrupted

workThreadA 종료됨

 

※ myGroup 스레드 그룹의 interrupt() 메소드 호출의 결과는 매 실행시 출력 순서가 달라질 수 있습니다.

 

 

ThreadGroupExample.java의 mainGroup.list() 에서 list() 메소드는 현재 스레드 그룹의 이름과 최대 우선순위를 헤더로 출력하고, 그 아래에 현재 스레드 그룹에 포함된 스레드와 하위 스레드 그룹의 내용을 보여줍니다.

스레드의 경우 [스레드 이름, 우선순위, 소속 그룹명]으로 출력되는 것을 확인할 수 있습니다.

 

또한 interrupt() 메소드를 호출하면 myGroup에 포함된 두 스레드에서 InterruptedException이 발생되어 스레드가 종료되는 것을 알 수 있습니다.

 

 

데몬 스레드에 대하여 알아보도록 하겠습니다.

 

일반적으로 데몬이라는 말은 리눅스나 유닉스등의 데몬 프로세스에서 많이 들어 보셨을 거라고 생각합니다.

ex) sshd, httpd - d는 daemon

 

daemon을 검색해보면 

멀티태스킹 운영체제에서 데몬은 사용자가 직접적으로 제어하지 않고, 백그라운드에서 돌면서 여러 작업을 하는 프로그램을 말한다.

라고 설명이 나와있습니다.

※ 정의 출처 : 데몬 (컴퓨팅) - 위키백과, 우리 모두의 백과사전

 

데몬 (컴퓨팅) - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 멀티태스킹 운영 체제에서 데몬(daemon, 발음: 데이먼/'deɪmən/ 또는 디먼 /'dimən/[1])은 사용자가 직접적으로 제어하지 않고, 백그라운드에서 돌면서 여러 작업을 하는 프로그램을 말한다. 시스템 로그를 남기는 syslogd처럼 보통 데몬을 뜻하는 ‘d’를 이름 끝에 달고 있으며, 일반적으로 프로세스로 실행된다. 데몬은 대개 부모 프로세스를 갖지 않으며, 즉 PPID가 1이며, 따라서 프로세스 트리에서 i

ko.wikipedia.org

그런데 자바의 데몬 스레드의 정의는 다음과 같이 명시 되어 있습니다(이것이 자바다 기준)

 

데몬(daemon) 스레드는 주 스레드의 작업을 돕는 보조적인 역할을 수행하는 스레드이다.

 

처음 데몬 정의에 나와있던 것을 바탕으로 생각해보면 데몬이라는 것은 syslogd 처럼 운영체제의 시스템 로그를 기록하는 작업을 돕는 역할을 수행하는 프로세스라고 생각할 수 있습니다.

 

마찬가지로 자바로 데몬 스레드도  프로그래밍을 해주면 우리가 일반적으로 알고 있는 데몬 프로세스처럼 사용할 수 있습니다.

 


데몬 스레드의 정의를 조금 더 알아보도록 하겠습니다.

 

1. 주 스레드의 작업을 돕는 보조적인 역할을 수행하는 스레드

2. 주 스레드가 종료되면 데몬 스레드는 강제적으로 자동 종료된다.(주 스레드의 보조 역할을 수행하므로 주 스레드가 종료되면 데몬 스레드의 존재 의미가 없어지기 때문)

 

이 2가지를 제외하면 데몬 스레드는 일반 스레드와 크게 차이가 없습니다.

 

데몬 스레드의 적용 예

 

1. 워드프로세서의 자동 저장

2. 미디어 플레이어의 동영상 및 음악 재생

 

등이 있는데, 이 기능들은 주 스레드(워드프로세스, 미디어 플레이어, JVM)가 종료되면 같이 종료됩니다.

 


데몬 스레드를 만들어 보도록 하겠습니다.

 

스레드를 데몬으로 만들기 위해서는 주 스레드가 데몬이 될 스레드의 setDaemon(true)를 호출해 주면 됩니다.

 

주의점은 스레드를 실행하는 start() 메소드가 호출되고 나서 setDaemon(true)를 호출하면 IllegalThreadStateException이 발생하기 때문에 start() 메소드 호출 전에 setDaemon(true)를 호출해야 합니다.

 

또한 현재 실행 중인 스레드가 데몬 스레드인지 아닌지를 구별하는 방법은 isDaemon() 메소드의 리턴값을 조사해보면 됩니다.

 

데몬 스레드일 경우 true를 리턴합니다.

 

 

예제를 통해 직접 확인해 보도록 하겠습니다.

 

 

 

 

 

AutoSaveThread.java - 1초 주기로 save() 메소드를 호출하는 데몬 스레드

package thread_state_control_4;
 
public class AutoSaveThread extends Thread {
 
    public void save() {
        System.out.println("작업 내용을 저장함.");
    }
    
    @Override
    public void run() {
        while(true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                break;
            }
            save();
        }
    }
}

 

 

 

DaemonExample.java - 메인 스레드가 실행하는 코드

 

package thread_state_control_4;
 
public class DaemonExample {
 
    public static void main(String[] args) {
        AutoSaveThread autoSaveThread = new AutoSaveThread();
        autoSaveThread.setDaemon(true);        //    AutoSaveThread를 데몬 스레드로 만듬
        autoSaveThread.start();
        
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
        }
        
        System.out.println("메인 스레드 종료");
    }
}

 

 

이 예제를 실행하게 되면 다음과 같은 결과를 확인할 수 있습니다.

 

작업 내용을 저장함.

작업 내용을 저장함.

메인 스레드 종료

 

교재에서는 "작업 내용을 저장함." 이 3번 출력된 결과를 보였는데 저는 2번 출력 되었습니다.

 


※ 간단하게 알아 보았지만 추후 추가 할 예제 등이 있으면 추가 하도록 하겠습니다.

 

 

제가 공부하며 정리하는 글에서 마지막으로 구분한 스레드 상태 제어 part 3입니다.

 

 

스레드 상태, 스레드 상태 제어 part 1, 2는 다음의 링크를 통해 제 글을 확인하고 와주세요!

 

 

 

 

Java - 스레드 상태(Thread State)

 

Java - 스레드 상태 제어 (Thread State Control) part 1

 

Java - 스레드 상태 제어 (Thread State Control) part 2

 


 

part 3에서는 정지와 관련된 스레드의 안전한 종료(stop 플래그, interrupt())에 대해서 알아보도록 하겠습니다.

 

 

 

스레드는 자신의 run() 메소드가 모두 실행되면 자동적으로 종료 됩니다.

(저번 글(Java - 스레드 상태 제어 (Thread State Control) part1)에서 notify()와 wait()의 사용 예제를 작성해보며 run()메소드가 모두 실행이 되었음에도 불구하고, 계속 살아 있었던 경우가 있었습니다(자동적으로 종료 되지 않았습니다). 그 부분을 이번 글에서 나온 내용을 바탕으로 해결하였습니다.)

 

 

스레드 상태에 대하여 작성했던 내용 중에 stop() 메소드를 사용하면 스레드를 즉시 종료시킬 수 있다고 했었는데, 그 메소드는 deprecated 되었다고 했습니다. 그 이유는 stop() 메소드로 스레드를 갑자기 종료하게 되면 스레드가 사용 중이던 자원들이 불안전한 상태로 남겨지기 때문이라고 합니다.

 

 

stop 메소드 대신 stop 플래그를 이용하는 방법과 interrupt() 메소드를 이용하는 방법이 있습니다.


우선 stop 플래그를 이용하는 방법에 대하여 알아보겠습니다.

 

다음과 같이 간단하게 이해하시면 되지 않을까 싶습니다.

 

===============

while(조건) {

    실행 문장

}

===============

 

위의 while 문에서 조건 부분이 이면 실행이 되고, 거짓이면 실행이 되지 않는 점을 이용합니다.

 

예를들어 다음과 같이 활용할 수 있습니다.

 

===============

private boolean stop;

 

public void run() {

    while ( !stop ) {

        스레드가 반복 실행하는 코드;

    }

    //스레드가 사용한 자원 정리

}

===============

 

이처럼 stop이 true가 되면 while이 종료됩니다.

while을 빠져나오게 되면 스레드가 사용한 자원을 정리하게 되고, run() 메소드가 끝나게 되면서 스레드는 안전하게 종료됩니다.

 

다음 예제를 통해 stop 플래그를 사용해 보도록 하겠습니다.

 

 

 

 

 

StopFlagExample.java - 1초 후 출력 스레드를 중지시킴

package thread_state_control_3;
 
public class StopFlagExample {
 
    public static void main(String[] args) {
        PrintThread1 printThread = new PrintThread1();
        printThread.start();
        
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
        
        printThread.setStop(true);    // 스레드를 종료시키기 위해 stop 필드를 true로 
    }
}

 

 

 

 

PrintThread1.java - 무한 반복해서 출력하는 스레드

package thread_state_control_3;
 
public class PrintThread1 extends Thread {
 
    private boolean stop;
    
    public void setStop(boolean stop) {
        this.stop = stop;
    }
    
    @Override
    public void run() {
        while(!stop) {
            System.out.println("실행 중");
        }
        System.out.println("자원 정리");    // stop이 true가 될 때
        System.out.println("실행 종료");
    }
}

 

예제를 실행하게 되면 다음과 같은 결과를 나오는 것을 확인할 수 있습니다.

 

실행 중

실행 중

실행 중

...

실행 중

자원 정리

실행 종료

 


다음으로 interrupt() 메소드를 이용하는 방법에 대하여 알아보도록 하겠습니다.

 

- interrupt() 메소드는 스레드가 일시 정지 상태에 있을 때 InterruptedException 예외를 발생시키는 역할을 합니다.

 

말보다 눈으로 직접 보는게 나을 수도 있지요!

 

다음의 [그림]을 보도록 하겠습니다.

 

[그림 interrupt() 발생 시 스레드의 상태]

ThreadA가 ThreadB의 interrupt() 메소드를 실행하게 되면 ThreadB가 sleep() 메소드로 일시 정지 상태가 될 때 ThreadB에서 InterruptedException이 발생하여 예외 처리(catch) 블록으로 이동합니다.

 

결과적으로 ThreadB는 while문을 빠져나와 run() 메소드를 정상 종료하게 됩니다.

* interrupt() 메소드가 sleep()(일시 정지)을 발생 시키지 않습니다! interrupt()라는 신호를 보내주는 거에요!

* 스레드 상태 제어 part 2 에서 이러한 방법을 이용하여 스레드를 종료하였습니다.

 

 

다음 예제를 통하여 사용해 보겠습니다.

 

 

 

InterruptExample - 1초 후 출력 스레드를 중지시킴

package thread_state_control_3;
 
public class InterruptExample {
 
    public static void main(String[] args) {
        Thread thread = new PrintThread2();
        thread.start();
        
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
        
        thread.interrupt();    // 스레드를 종료시키기 위해 InterruptedException을 발생시킴
    }
}

 

 

 

PrintThread2.java - 무한 반복해서 출력하는 스레드

package thread_state_control_3;
 
public class PrintThread2 extends Thread {
 
    @Override
    public void run() {
        try {
            System.out.println("실행 중");
            Thread.sleep(1);    // InterruptedException 발생
        } catch (InterruptedException e) {
        }
        
        System.out.println("자원 정리");
        System.out.println("실행 종료");
    }
}

 

★ 여기서 주목하실 점은 스레드가 실행 대기 또는 실행 상태에 있을 때 interrupt() 메소드가 실행되면 즉시 InterruptedException 예외가 발생하는게 아닙니다!

    스레드가 미래에 일시 정지 상태가 되면 InterruptedException 예외가 발생합니다. 그러므로 스레드가 일시 정지 상태가 되지 않으면 interrupt() 메소드 호출은 아무런 의미가 없습니다.

 

 

일시 정지를 하지 않고도 interrupt() 호출 여부를 알 수 있는 방법이 있습니다.

interrupt() 메소드가 호출되었다면 스레드의 interrupted()와 isInterrupted() 메소드는 true를 리턴합니다.

 

바로 위 예제의 PrintThread2.java 를 수정하여 interrupted를 사용하도록 해보았습니다.

 

 

 

 

PrintThread2.java - 수정된 코드

package thread_state_control_3;
 
public class PrintThread2 extends Thread {
 
    @Override
    public void run() {
        while(true) {
            System.out.println("실행 중");
            if(Thread.interrupted()) {
                break;
            }
        }
        
        System.out.println("자원 정리");
        System.out.println("실행 종료");
    }
}

 

interrupted()를 stop 플래그 처럼 활용하는 방법입니다.

 

 


스레드 상태 제어는 세분화하면 더욱 다양하지만 저는 일단 여기까지를 기본 개념으로 보고 있습니다.

나머지는 상태 제어는 맞지만 개념 자체가 상태 제어도 포함하지만 어떻게 보면 다른 개념처럼 보일 수도 있기에 따로 개념을 정리하였습니다.

 

이후 데몬 스레드, 스레드 그룹, 스레드풀의 형식으로 글을 작성하도록 하겠습니다.

 

 

 

 

 

 

스레드 상태 제어 part2입니다.

 

스레드 상태와 스레드 상태 제어 part1은 다음의 링크를 통해 제 글을 보고 와주세요!

 

 

 

 

 

Java - 스레드 상태(Thread State)

 

 

Java - 스레드 상태 제어 (Thread State Control) part 1

 

 

 

 

여러분들은 같이 하는게 좋으신가요?

아니면 혼자 하는게 좋으신가요?

 

이에 대한 정답은 없고, 상황에 따라 다르다고 할 수 있을 것 같습니다.

 

그럼에도 같이 해야 하는 경우가 있습니다.

 

바로 이번에 다루게 될 스레드 간 협업(wait(), notify(), notifyAll())이 바로 그 대상입니다.

 

상황에 따라서 두 개의 스레드를 교대로 번갈아가며 실행해야 하는 경우가 있습니다.

 

정확한 교대 작업이 필요할 경우,

- 자신의 작업이 끝나면 상대방 스레드를 일시 정지 상태에서 풀어주고!

- 자신은 일시 정지 상태로 만듭니다.

 

이 방법의 핵심은 공유 객체에 있습니다.

 

이와 관련하여 다음의 [그림 1]을 통해 알아 보겠습니다.

 

[그림 1 - 스레드 간 협업(wait(), notify(), notifyAll())]

 

공유 객체는 두 스레드가 작업할 내용을 각각 동기화 메소드로 구분해 놓습니다.

 

한 스레드가 작업을 완료하면 notify() 메소드를 호출해서 일시 정지 상태에 있는 다른 스레드를 실행 대기 상태로 만들고,

자신은 두 번 작업을 하지 않도록 wait() 메소드를 호출하여 일시 정지 상태로 만듭니다.

 

- wait() 대신에 wait(long timeout)이나, wait(long timeout, int nanos)를 사용하면 nofity()를 호출하지 않아도 지정된 시간이 지나면 스레드가 자동적으로 실행 대기 상태가 됩니다.

 

notify()와 동일한 역할을 하는 notifyAll() 메소드도 있는데, notify()는 wait()에 의해 일시 정지된 스레드 중 한 개를 실행 대기 상태로 만듭니다.

 

notifyAll() 메소드는 wait()에 의해 일시 정지된 모든 스레드들을 실행 대기 상태로 만듭니다.

 

wait(), notify(), notifyAll() 메소드는 Thread 클래스가 아닌 Object 클래스에 선언된 메소드이므로 모든 공유 객체에서 호출이 가능하다고 합니다.

주의 점은 이 메소드들은 동기화 메소드 또는 동기화 블록 내에서만 사용할 수 있다고 합니다.

 

 

 

예제를 통해 사용해 보도록 하겠습니다.

 

 

 

 

 

WorkObject.java - 두 스레드의 작업 내용을 동기화 메소드로 작성한 공유 객체

package thread_state_control_2;
 
public class WorkObject {
 
    public synchronized void methodA() {
        System.out.println("ThreadA의 methodA() 작업 실행");
        notify();    // 일시 정지 상태에 있는 ThreadB를 실행 대기 상태로 만듬
        try {
            wait();    // ThreadA를 일시 정지 상태로 만듬
        } catch (InterruptedException e) {
        }
    }
    
    public synchronized void methodB() {
        System.out.println("ThreadB의 methodB() 작업 실행");
        notify();    // 일시 정지 상태에 있는 ThreadA를 실행 대기 상태로 만듬
        try {
            wait();    // ThreadB를 일시 정지 상태로 만듬
        } catch (InterruptedException e) {
        }
    }
}
 

 

 

 

ThreadA.java - WorkObject의 methodA()를 실행하는 스레드

package thread_state_control_2;
 
public class ThreadA extends Thread {
 
    private WorkObject workObject;
    
    public ThreadA(WorkObject workObject) {
        this.workObject = workObject;    // 공유 객체를 매개값으로 받아 필드에 저장
    }
    
    @Override
    public void run() {
        for(int i=0; i<10; i++) {
            workObject.methodA();        // 공유 객체의 methodA()를 10번 반복 호출
        }
    }
}
 

 

 

 

ThreadB.java - WorkObject의 methodB()를 실행하는 스레드

package thread_state_control_2;
 
public class ThreadB extends Thread {
 
    private WorkObject workObject;
    
    public ThreadB(WorkObject workObject) {
        this.workObject = workObject;    // 공유 객체를 매개값으로 받아 필드에 저장
    }
    
    @Override
    public void run() {
        for(int i=0; i<10; i++) {
            workObject.methodB();        // 공유 객체의 methodB()를 10번 반복 호출
        }
    }
}
 

 

 

 

 

WaitNotifyExample.java - 두 스레드를 생성하고 실행하는 메인 스레드

package thread_state_control_2;
 
public class WaitNotifyExample {
 
    public static void main(String[] args) {
        WorkObject sharedObject = new WorkObject();        // 공유 객체 생성
        
        // ThreadA와 ThreadB 생
        ThreadA threadA = new ThreadA(sharedObject);
        ThreadB threadB = new ThreadB(sharedObject);
        
        // ThreadA와 ThreadB를 실행
        threadA.start();
        threadB.start();
    }
}

 

 

예제를 실행해 보시면 다음과 같이

 

ThreadA의 methodA() 작업 실행

ThreadB의 methodB() 작업 실행

 

ThreadA와 ThreadB를 10번씩 실행하는 것을 확인하실 수 있습니다.

 

하지만 위의 예제를 그대로 실행하면 프로세스 종료가 되지 않습니다. (?!)

 

예전에 프로젝트 진행하면서 실행 후 종료되지 않은 상태로 계속 몇 번이고 실행 시켰던 적이 있었는데

당시에 그로 인해서 컴퓨터 자원을 쓸 수가 없어서 컴퓨터가 먹통이 될 정도로 느려져 버린 적이 있었습니다.

 

그래서 이번에는 실수하지 않고, 종료가 되지 않는 것을 확인하고, 일일이 수동으로 꺼줬는데 너무 불편하고,

우리는 개발자이기 때문에 이것도 프로그래밍으로 해결을 해야지! 라는 생각으로 다음과 같이 수정을 했습니다.

수정된 소스 코드에 들어간 내용은 스레드의 상태 제어 part 3에서 다루는 내용을 사용하였습니다.

 

 

 

 

 

ThreadA.java - 수정된 코드

package thread_state_control_2;
 
public class ThreadA extends Thread {
 
    private WorkObject workObject;
    
    public ThreadA(WorkObject workObject) {
        this.workObject = workObject;    // 공유 객체를 매개값으로 받아 필드에 저장
    }
    
    @Override
    public void run() {
        for(int i=0; i<10; i++) {
            workObject.methodA();        // 공유 객체의 methodA()를 10번 반복 호출
        }
        
 
        // 수정된 부분
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
        }
    }
}

 

 

 

ThreadB.java - 수정된 코드

package thread_state_control_2;
 
public class ThreadB extends Thread {
 
    private WorkObject workObject;
    
    public ThreadB(WorkObject workObject) {
        this.workObject = workObject;    // 공유 객체를 매개값으로 받아 필드에 저장
    }
    
    @Override
    public void run() {
        for(int i=0; i<10; i++) {
            workObject.methodB();        // 공유 객체의 methodB()를 10번 반복 호출
        }
        
        // 수정된 부분
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
        }
    }
}

 

 

 

 

WaitNotifyExample.java - 수정된 코드

package thread_state_control_2;
 
public class WaitNotifyExample {
 
    public static void main(String[] args) {
        WorkObject sharedObject = new WorkObject();        // 공유 객체 생성
        
        // ThreadA와 ThreadB 생
        ThreadA threadA = new ThreadA(sharedObject);
        ThreadB threadB = new ThreadB(sharedObject);
        
        // ThreadA와 ThreadB를 실행
        threadA.start();
        threadB.start();
        
 
        // 수정된 부분
        try {
            System.out.println("Testing");
            Thread.sleep(1000);
            System.out.println("Testing");
        } catch (InterruptedException e) {}
        
        threadA.interrupt();
        threadB.interrupt();
    }
}

 

3개 파일을 수정해 줬습니다.

제가 정확히 이해를 하지 못해서 이런 식으로 했다고 생각합니다.

마지막 WaitNotifyExample.java의 println의 경우,

처음에 어떤 스레드에서 종료가 되지 않는지 해서 디버깅 해보니

ThreadA, ThreadB가 10번 실행되고, 종료가 되지 않았습니다.

 

그래서 Thread.sleep() 으로 잠깐이라도 멈춰 주려고 했는데 여기서 또 어디서 interrupt()를 걸어야 할지 몰라서

예외 처리 구문에서 확인 할 수 있듯이 try 부분에서 Thread.sleep()을 기준으로 println을 출력해 봤습니다.

 

실행 결과는

 

Testing

// 다음 2줄은 10번 반복입니다.

ThreadA의 methodA() 작업 실행

ThreadB의 methodB() 작업 실행

...

Testing

 

이와 같이 출력이 되는 것을 확인 했습니다.

part 3에서 설명 드리겠지만 interrupt()가 발생하면 각각의 작업 스레드(ThreadA, ThreadB)에서는 try 구문에 있는 Thread.sleep()을 실행하게 됩니다.

 

그렇게 해당 스레드의 사용을 정리시키고, 최종적으로 모든 스레드가 종료되는 것을 확인 할 수 있었습니다.

 

제 방법이 가장 정확한 것도 아니고, 더 좋은 방법이 있는지 찾아 봐야겠습니다. (피드백은 언제든지 환영입니다!)

 

 

 

 

 

예제를 하나 더 해보도록 하겠습니다.

 

우선 [그림 2]를 보고 설명을 드리도록 하겠습니다.

 

[그림 2 - 생산자, 소비자 스레드]

 

위 그림을 통해 사용할 예제는 다음과 같습니다.

 

- 데이터를 저장하는 스레드(생산자 스레드)가 데이터를 저장(생산)합니다.

- 데이터를 소비하는 스레드(소비자 스레드)가 데이터를 읽고 처리합니다.

 

이 2가지를 처리하는 교대 작업을 구현한 예제입니다.

 

이번 예제의 핵심은 다음과 같습니다.

 

- 생성자 스레드는 소비자 스레드가 읽기 전에 새로운 데이터를 두번 생성하면 안 됩니다.(setData() 메소드 두 번 실행 X)

- 소비자 스레드는 생성자 스레드가 새로운 데이터를 생성하기 전에 이전 데이터를 두 번 읽어서도 안 됩니다.(getData() 메소드 두 번 실행X)

 

 

 

예제로 넘어가도록 하겠습니다.

 

 

 

 

DataBox.java - 두 스레드의 작업 내용을 동기화 메소드로 작성한 공유 객체

package thread_state_control_3;
 
public class DataBox {
 
    private String data;
    
    public synchronized String getData() {
        
        //    data 필드가 null이면 소비자 스레드를 일시 정지 상태로 만듬
        if(this.data == null) {
            try {
                wait();
            } catch(InterruptedException e) {}
        }
        String returnValue = data;
        System.out.println("ConsummerThread가 읽은 데이터: " + returnValue);
        
        //    data 필드를 null로 만들고 생산자 스레드를 실행 대기 상태로 만듬
        data = null;
        notify();
        return returnValue;
    }
    
    public synchronized void setData(String data) {
        
        //    data 필드가 null이 아니면 생산자 스레드를 일시 정지 상태로 만듬
        if(this.data != null) {
            try {
                wait();
            } catch(InterruptedException e) {}
        }
        
        //    data 필드에 값을 저장하고 소비자 스레드를 실행 대기 상태로 만듬
        this.data = data;
        System.out.println("ProducerThread가 생성한 데이터: " + data);
        notify();
    }
}

 

 

 

 

 

ProducerThread.java - 데이터를 생산(저장)하는 스레드

package thread_state_control_3;
 
public class ProducerThread extends Thread {
 
    private DataBox dataBox;
    
    public ProducerThread(DataBox dataBox) {
        this.dataBox = dataBox;        //    공유 객체를 필드에 저장
    }
    
    @Override
    public void run() {
        for(int i=1; i<=3; i++) {
            String data = "Data-" + i;
            dataBox.setData(data);    //    새로운 데이터를 저장
        }
    }
}

 

 

 

 

 

ConsumerThread.java - 데이터를 소비하는(읽는) 스레드

package thread_state_control_3;
 
public class ConsumerThread extends Thread {
 
    private DataBox dataBox;
    
    public ConsumerThread(DataBox dataBox) {
        this.dataBox = dataBox;        //    공유 객체를 필드에 저장
    }
    
    @Override
    public void run() {
        for(int i=1; i<=3; i++) {
            String data = dataBox.getData();        //    새로운 데이터를 읽음
        }
    }
}

 

 

 

 

 

WaitNotifyExample.java - 두 스레드를 생성하고 실행하는 메인 스레드

package thread_state_control_3;
 
public class WaitNotifyExample {
 
    public static void main(String[] args) {
        DataBox dataBox = new DataBox();
        
        ProducerThread producerThread = new ProducerThread(dataBox);
        ConsumerThread consumerThread = new ConsumerThread(dataBox);
        
        producerThread.start();
        consumerThread.start();
    }
}

 

두 번째 예제를 실행하시면 다음과 같이 나오시는 것을 확인하실 수 있습니다.

 

ProducerThread가 생성한 데이터: Data-1

ConsummerThread가 읽은 데이터: Data-1

ProducerThread가 생성한 데이터: Data-2

ConsummerThread가 읽은 데이터: Data-2

ProducerThread가 생성한 데이터: Data-3

ConsummerThread가 읽은 데이터: Data-3

 

 

저는 아직이라고 생각하지만 다른 분들께서는 여기까지 오시면서 다 이해하셨을 거라고 생각합니다!

 

제 기준에서 가장 이해하기 좋은 방법은 제공되는 예제소스를 Ctrl + V / Command + V 하지 않고, 직접 입력해가며 실행해 보는 거라고 생각합니다.

 

그러면서 조금씩 수정이 필요한 부분이나 추가할 곳에 추가도 해주면서 해봐야 이해도 잘 되고 실력도 늘게 된다고 생각합니다.

 

이렇게 Java - 스레드 상태 제어 (Thread State Control) part 2를 마치도록 하겠습니다.

 

 

 

이번에는 스레드 상태에 이어서 스레드 상태 제어에 대하여 알아 보겠습니다.

 

스레드 상태에 대한 부분은 다음의 링크를 통해 제 글을 보고 와주세요!

(제 설명이 부족했을 수도 있으니 검색을 통해서 본인이 알아보고 오셔도 좋습니다!)

 

Java - 스레드 상태(Thread State)

 

Java - 스레드 상태(Thread State)

이번에는 스레드 상태에 대해서 알아보도록 하겠습니다. 스레드 프로그래밍을 진행하다보면 동기화/비동기화 뿐만 아니라 스레드의 행동을 직접 제어해야 할 경우가 생깁니다. 1000장의 문서를 복사해야한다고 가..

widevery.tistory.com

스레드 상태 제어는 총 3개의 part로 나누어서 진행할 예정입니다.

 

스래드의 상태 중 실행, 실행 대기, 일시 정지 3가지를 조금 더 구체적으로 살펴보면 다음과 같습니다.

 

[그림 1 - 스레드의 실행, 실행 대기, 일시 정지의 구체적인 상태]

 

그림 1 에서 잘 보이지 않지만 취소선이 있는 suspend(), resume(), stop() 이 3가지는 스레드의 안전성을 해친다고 하여 더 이상 사용하지 않도록 권장된 Deprecated 메소드라고 합니다.

 

 

스레드 상태 제어와 관련된 메소드들을 다음의 표를 통하여 알아보도록 하겠습니다.

 

메소드 설명
interrupt() 일시 정지 상태의 스레드에서 InterruptedException 예외를 발생시켜, 예외 처리 코드(catch)에서 실행 대기 상태로 가거나 종료 상태로 갈 수 있도록 한다.

notify()

notifyAll()

동기화 블록 내에서 wait() 메소드에 의해 일시 정지 상태에 있는 스레드를 실행 대기 상태로 만든다.
resume()

suspend(0 메소드에 의해 일시 정지 상태에 있는 스레드를 실행 대기 상태로 만든다.

 - Deprecated (대신 notify(), notifyAll() 사용)

sleep(long millis)

sleep(long millis, int nanos)

주어진 시간 동안 스레드를 일시 정지 상태로 만든다. 주어진 시간이 지나면 자동적으로 실행 대기 상태가 된다.

join()

join(long millis)

join(long millis, int nanos)

join() 메소드를 호출한 스레드는 일시 정지 상태가 된다. 실행 대기 상태로 가려면, join() 메소드를 멤버로 가지는 스레드가 종료되거나, 매개값으로 주어진 시간이 지나야 한다.

wait()

wait(long millis)

join(long millis, int nanos)

동기화(synchronized) 블록 내에서 스레드를 일시 정지 상태로 만든다. 매개값으로 주어진 시간이 지나면 자동적으로 실행 대기 상태가 된다. 시간이 주어지지 않으면 notify(), notifyAll() 메소드에 의해 실행 대기 상태로 갈 수 있다.
suspend() 스레드를 일시 정지 상태로 만든다. resume() 메소드를 호출하면 다시 실행 대기 상태가 된다. - Deprecated (대신 wait() 사용)
yield() 실행 중에 우선순위가 동일한 다른 스레드에게 실행을 양보하고 실행 대기 상태가 된다.
stop() 슬드를 즉시 종료시킨다. - Deprecated

 

위 표에서 wait(), notify(), notifyAll() 은 Object 클래스의 메소드이고, 그 이외의 메소드는 Thread 클래스의 메소드입니다.

 

이번 글에서는 sleep(), yield(), join() 메소드에 대하여 알아 보도록 하겠습니다.

 

 

 

 

 

1. 주어진 시간동안 일시 정지(sleep())

 

   · Thread.sleep() 메소드를 호출한 스레드는 주어진 시간 동안 일시 정지 상태가 되고, 다시 실행 대기 상태로 돌아갑니다.

   · 매개값에는 얼마 동안 일시 정지 상태로 있을 것인지, 밀리세컨드(1/1000) 단위로 시간을 주면 됩니다. ex) Thread.sleep(3000); ->3초

   · 일시 정지 상태에서 주어진 시간이 되기 전에 interrupt() 메소드가 호출되면 InterruptedException이 발생하기 때문에

     예외 처리가 필요합니다.

 

 

 

다음은 sleep() 예제 입니다.

 

 

 

SleepExample.java - 3초 주기로 10번 비프음 발생

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package thread_state_control;
 
 
public class SleepExample {
 
    public static void main(String[] args) {
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        for(int i=0; i<10; i++) {
            toolkit.beep();
            try {
                Thread.sleep(3000);    // 3초 동안 메인 스레드를 일시 정지 상태를 만듭니다.
            } catch(InterruptedException e) {
                
            }
        }
    }
}
 

 

실행해보면 정상적으로 소리가 3초 간격으로 10번 동안 나는 것을 확인 하실 수 있습니다.

 

 

 

 

 

2. 다른 스레드에게 실행 양보(yield())

 

   · yield()는 실행 되고 있는 스레드 주체가 혼자만 작동하지 않고, 다른 스레드가 실행 기회를 가질 수 있도록 해줍니다.

   · yield()는 sleep()과 함께 글의 마지막 부분에 제 나름대로의 설명을 추가하도록 하겠습니다.

 

 

다음은 yield()를 사용 했을 때의 스레드들의 상태 변화를 나타낸 것입니다.

 

[그림 2 - yield()로 인한 스레드의 상태 변화]

 

다음은 yield() 예제 입니다.

 

 

 

YieldExample.java - 스레드 실행 양보 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package thread_state_control;
 
public class YieldExample {
 
    public static void main(String[] args) {
        ThreadA threadA = new ThreadA();
        ThreadB threadB = new ThreadB();
        // ThreadA, ThreadB 모두 실행
        threadA.start();
        threadB.start();
        
        try { Thread.sleep(3000); } catch (InterruptedException e) {}
        threadA.work = false;    // ThreadB만 실행
        
        try { Thread.sleep(3000); } catch (InterruptedException e) {}
        threadA.work = true;        // ThreadA, ThreadB 모두 실
        
        try { Thread.sleep(3000); } catch (InterruptedException e) {}
        // ThreadA, ThreadB 모두 종료
        threadA.stop = true;
        threadB.stop = true;
    }
}
 

 

ThreadA.java - 스레드 실행 양보 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package thread_state_control;
 
public class ThreadA extends Thread {
 
    public boolean stop = false;
    public boolean work = true;
    
    @Override
    public void run() {
        while(!stop) {    // stop이 true가 되면 while문 종료
            if(work) {
                System.out.println("ThreadA 작업 내용");
            } else {
                Thread.yield();    // work가 false가 되면 다른 스레드에게 실행 양보
            }
        }
        System.out.println("ThreadA 종료");
    }
}
 

 

ThreadB.java - 스레드 실행 양보 예제 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package thread_state_control;
 
public class ThreadB extends Thread {
 
    public boolean stop = false;
    public boolean work = true;
    
    @Override
    public void run() {
        while(!stop) {    // stop이 true가 되면 while문 종료
            if(work) {
                System.out.println("ThreadB 작업 내용");
            } else {
                Thread.yield();    // work가 false가 되면 다른 스레드에게 실행 양보
            }
        }
        System.out.println("ThreadB 종료");
    }
}
 

 

실행을 하게 되면 ThreadA와 ThreadB가 번갈아가며 실행이 되다가 yield()로 인하여 ThreadB가 더 많은 실행 기회를 가지게 되어서

ThreadB만 실행이 되는 상황이 나옵니다.

그러다 다시 ThreadA와 ThreadB가 번갈아가며 실행이 되면서 결국 마지막에 모든 스레드가 종료가 됩니다.

 

 

 

 

 

 

3. 다른 스레드의 종료를 기다림(join())

 

   · 스레드는 다른 스레드와 독립적으로 실행하는 것이 기본입니다.

   · 하지만 다른 스레드가 종료될 때까지 기다렸다가 실행해야 하는 경우가 발생할 수도 있습니다. 

   · join() 메소드는 sleep(), yield() 와 함께 마지막 부분에서 제 나름대로의 방식으로 설명을 추가하겠습니다.

 

 

다음 그림은 join()을 사용 했을 때의 스레드 상태를 나타낸 것입니다.

 

[그림 3 - join() 사용시 스레드의 상태]

 

 

다음은 yield()를 사용한 예제 입니다.

 

 

 

SumThread.java - 1부터 100까지 합을 계산하는 스레드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package thread_state_control;
 
public class SumThread extends Thread {
 
    private long sum;
    
    public long getSum() {
        return sum;
    }
    
    public void setSum(long sum) {
        this.sum = sum;
    }
    
    @Override
    public void run() {
        for(int i=1; i<=100; i++) {
            sum+=i;
        }
    }
}
 

 

JoinExample.java - 다른 스레드가 종료될 때까지 일시 정지 상태 유지

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package thread_state_control;
 
public class JoinExample {
 
    public static void main(String[] args) {
        SumThread sumThread = new SumThread();
        sumThread.start();
        
        try {
            sumThread.join();    // sumThread가 종료할 때까지 메인 스레드를 일시 정지시킴
        } catch(InterruptedException e) {
        }
        
        System.out.println("1~100 합: " + sumThread.getSum());
    }
}
 

 

join() 예제를 실행해 보시면 정상적으로 5050이 출력 되는 것을 확인 하실 수 있습니다.

하지만 JoinExample의 9 ~ 12 행인 try ~ catch 구문을 주석처리 하고 실행하면 0이 나오게 됩니다.

(교재에서는 컴퓨터 성능에 따라서 다른 값이 출력 될 수도 있다고 합니다. 저는 0이 나왔습니다.)

이러한 이유는 SumThread가 계산 작업을 완료하지 않은 상태에서 합을 먼저 출력하기 때문입니다.

 

이렇듯 sleep(), yield(), join()에 대하여 알아 보았습니다.

 

개인적으로 sleep() yield() join()이 다 비슷비슷 하게 느껴져서 헷갈리지만 제 나름대로의 생각은 다음과 같습니다.

 

1. sleep()의 예시

   - 혼자서 프린터를 사용하여 문서 1000장을 복사하는데 너무 지치니까 100장 마다 5분 씩 쉬고 다시 복사를 합니다.

2. yield()의 예시

   - 프린터를 사용하여 문서 1000장을 복사하는데 복사하는 도중, 다른 사람이 사용하려 하면 사용하도록 하고 자신은 잠시 쉽니다.

3. join()의 예시

   - 하나의 내용에 대한 문서가 2000장 입니다. 이 때, 1000장 씩 나눠서 역할을 분담 받았습니다.

     단, 2000장의 문서가 모두  모여야 내용이 완전해지고, 그렇지 않을 경우 의미 없고, 알 수 없는 내용의 문서가 되어 버립니다.

   - 결국 다른 사람이 담당하는 문서의 내용을 합하여 하나의 내용으로 만들어야 하기 때문에

     다른 사람이 담당하는 역할이 끝날 때까지 기다립니다.

 

대략 이런 식으로 sleep(), yield(), join()을 이해를 해 봤습니다. (정확한지는 모르겠습니다.)

피드백을 주시면 감사히 받겠습니다.

 

 

이번에는 스레드 상태에 대해서 알아보도록 하겠습니다.

 

스레드 프로그래밍을 진행하다보면 동기화/비동기화 뿐만 아니라 스레드의 행동을 직접 제어해야 할 경우가 생깁니다.

 

1000장의 문서를 복사해야한다고 가정합니다.

공유된 프린터는 1개 밖에 없는데, 이 때 1000장을 전부 복사하는 동안 혼자서 쓸 수 없을 겁니다.

다른 사람들도 프린터를 사용해야 하는 경우가 분명 생길거에요.

그럴 때, 100장씩 복사를 하도록 하고, 그 때마다 다른 사람들이 사용하도록 통제할 수도 있을거에요.

 

이처럼 스레드도 상태를 제어해야 할 필요성이 있는데, 제어를 하기 전에 해당 스레드의 상태를 알아야 합니다.

그러기 위해서는 getState() 메서드를 사용하여 상태를 확인합니다.

 

□ 스레드 상태

    · 스레드는 start() 하게 되면 일반적으로 다음과 같은 상태로 진행 됩니다.

[그림 1 - 스레드의 일반적인 실행 상태]

 

    · 경우에 따라서 스레드는 실행 상태에서 실행 대기 상태로 가지 않을 수도 있습니다.

    · 실행 상태에서 일시 정지 상태로 가기도 하는데, 일시 정지 상태는 스레드가 실행할 수 없는 상태입니다.

    · 스레드가 다시 실행 상태로 가기 위해서는 일시 정지 상태에서 실행 대기 상태로 가야합니다.

 

[그림 2 - 스레드의 실행과 일시 정지 상태]

 

이처럼 프로그래밍 하면서 스레드의 상태를 알 수 있도록 해주는 메소드는 getState() 입니다.

getState()의 스레드 상태에 따른 Thread.State 열거 상수가 있는데 열거 상수는 다음의 표와 같습니다.

 

상태 열거 상수 설명
객체 생성 NEW 스레드 객체가 생성, 아직 start() 메소드가 호출되지 않은 상태
실행 대기 RUNNABLE 실행 상태로 언제든지 갈 수 있는 상태
일시 정지 WAITING 다른 스레드가 통지할 때까지 기다리는 상태
TIMED_WAITING 주어진 시간 동안 기다리는 상태
BLOCKED 사용하고자 하는 객체의 락이 풀릴 때까지 기다리는 상태
종료 TERMINATED 실행을 마친 상태

 

이제 프로그래밍을 통해 스레드의 상태를 직접 확인해 봅시다.

 

이 예제는 스레드의 상태를 출력해주며, 생성자 매개값으로 받은 타겟 스레드의 상태를 0.5초 주기로 출력 해주는 클래스 입니다.

 

 

 

StatePrintThread.java - 타겟 스레드의 상태를 출력하는 스레드

package thread_state;

public class StatePrintThread extends Thread {

	private Thread targetThread;
	
	public StatePrintThread(Thread targetThread) {
		this.targetThread = targetThread;
	}
	
	@Override
	public void run() {
		while(true) {
			Thread.State state = targetThread.getState();	// 스레드 상태 얻기
			System.out.println("타겟 스레드 상태: " + state);
			
			// 객체 생성 상태일 경우 실행 대기 상태로 만듬
			if(state == Thread.State.NEW) {
				targetThread.start();
			}
			
			// 종료 상태일 경우 while문을 종료함
			if(state == Thread.State.TERMINATED) {
				break;
			}
			try {
				Thread.sleep(500);
			} catch(Exception e) {}
		}
	}
}

 

 

다음은 타겟 스레드 클래스입니다.

10억 번 루핑을 돌게 해서 RUNNABLE 상태를 유지하고, sleep() 메소드를 호출해서 1.5초간 TIMED_WAITING 상태를 유지합니다.

또한 다시 10억 번 루핑을 돌게 해서 RUNNABLE 상태를 유지합니다.

 

 

TargetThread.java - 타겟 스레드

package thread_state;

public class TargetThread extends Thread {

	@Override
	public void run() {
		for(long i=0; i<1000000000; i++) {}
		
		try {
			Thread.sleep(1500);
		} catch(Exception e) {}
		
		for(long i=0; i<1000000000; i++) {}
	}
}

 

TargetThread가 객체로 생성되면 NEW 상태를 가지고, run() 메소드가 종료되면 TERMINATED 상태가 되므로 결국 다음과 같은 상태로 변합니다.

 

NEW -> RUNNABLE -> TIMED_WAITING -> RUNNABLE -> TERMINATED

 

다음은 StatePrintThread를 생성해서 매개값으로 전달받은 TargetThread의 상태를 출력하는 클래스입니다.

 

 

 

ThreadStateExample.java - 실행 클래스

package thread_state;

public class ThreadStateExample {

	public static void main(String[] args) {
		StatePrintThread statePrintThread = 
				new StatePrintThread(new TargetThread());
		statePrintThread.start();
	}
}

 

실행을 하면

 

타겟 스레드 상태: NEW

타겟 스레드 상태: RUNNABLE

타겟 스레드 상태: RUNNABLE

타겟 스레드 상태: TIMED_WAITING

타겟 스레드 상태: TIMED_WAITING

타겟 스레드 상태: TIMED_WAITING

타겟 스레드 상태: RUNNABLE

타겟 스레드 상태: RUNNABLE

타겟 스레드 상태: RUNNABLE

타겟 스레드 상태: TERMINATED

 

이와 같은 실행 결과를 확인할 수 있다.. 고 이것이 자바다에 나와 있습니다.

 

저는 다음과 같은 상태변화만 확인할 수 있었습니다.

 

타겟 스레드 상태: NEW

타겟 스레드 상태: TIMED_WAITING

타겟 스레드 상태: TIMED_WAITING

타겟 스레드 상태: TIMED_WAITING

타겟 스레드 상태: RUNNABLE

타겟 스레드 상태: TERMINATED

 

제가 자세하게 안다면 이와 같은 현상에 대해서 자세하게 알 수 있을텐데 아직 많이 부족하여 잘 모르겠네요..

첫 번째 10억 번 루핑도는 부분에서 상태가 잡히지 않는 것 같습니다.

출력을 찍어보면 정상적으로 되는 것 같은데 해당 상태가 RUNNABLE이 맞다고 생각하는데 첫 번째 루핑 넘어간 다음 sleep()으로 바로 넘어가서 TIMED_WAITING이 출력되네요.

 

시스템 환경을 다양하게해서 확인해 봐야 할 것 같습니다.

 

 

안녕하세요 활짝웃자입니다.

Java / HTML5 / CSS / JSP / Spring / DataBase : Postgresql / Apache Tomcat을 모두 사용하게 되어서

기본 문법만 하고 프로그래밍 했었는데 필요하게 된 부분들이 많아져서 글을 작성하게 되었습니다.

 

이것이 자바다 교재를 사용하여 진행하였습니다.

총 2권으로 나누어져 있는데, 1권은 기본 문법 + Java에서 자주 사용되는 API등을 다루는 내용입니다.

2권은 멀티스레드, 제네릭, 람다식, 컬렉션 프레임워크, 스트림과 병렬 처리, 네티워킹 등 기본 문법을 숙지 후, 개발 업무 시 자주 사용되는 부분을 설명하는 내용입니다.

 

Java 게시글은 다음과 같은 방식으로 진행 됩니다.

1. 이것이 자바다 교재가 아니어도 Java 기본 문법을 알고 계신다고 생각하며 진행합니다.

2. 모든 내용을 다루지는 않을 것이며 참고한 부분은 분명하게 출처를 밝히면서 진행합니다.

 

 

 

멀티태스킹두 가지 이상의 작업을 동시에 처리하는 것입니다.

이때 사용되는 방법 중 하나인 멀티 스레드가 있습니다.

멀티스레드에서 동기화, 비동기화의 개념이 존재 하는데 해당 개념은 다음의 출처를 통해 확인해 주시기 바랍니다.

 

Synchronous(동기) vs Asynchronous(비동기)

 

Synchronous(동기) Vs Asynchronous(비동기)

 

nesoy.github.io

동기화를 실험해 보기 위해 비동기를 먼저 확인해 보도록 합시다.

 

 

Calculator.java - 공유객체

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
package thread_synchronized;
 
public class Calculator {
 
    private int memory;
    
    public int getMemory() {
        return memory;
    }
    
    public  void setMemory(int memory) {
        this.memory = memory;
        try {
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + ": " + this.memory);
        } catch(InterruptedException e) {
            /* 이것이 자바다 교재에서는 이 부분(catch)으로 실행이 되는 것 같지만
             * 제가 실행할 때는 정상적으로 출력이 되지 않아서
             * try 부분에 확인을 위한 코드를 삽입 하였습니다.
             */
            System.out.println(Thread.currentThread().getName() + ": " + this.memory);
        }
    }
}
 

 

 

User1.java - User1 스레드

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package thread_synchronized;
 
public class User1 extends Thread {
 
    private Calculator calculator;
    
    public void setCalculator(Calculator calculator) {
        this.setName("User1");
        this.calculator = calculator;
    }
    
    public void run() {
        calculator.setMemory(100);
    }
}
 

 

 

User2.java - User2 스레드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package thread_synchronized;
 
public class User2 extends Thread {
 
    private Calculator calculator;
    
    public void setCalculator(Calculator calculator) {
        this.setName("User2");
        this.calculator = calculator;
    }
    
    public void run() {
        calculator.setMemory(50);
    }
}
 

 

 

MainThreadExample.java - 메인 스레드가 실행하는 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package thread_synchronized;
 
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.setCalculator(calculator);
        user2.start();
    }
}

 

MainThreadExample.java를 실행하면 다음과 같은 결과를 확인 하실 수 있습니다.

 

User1: 50

User2: 50

 

위와 같이 동시에 실행하는 것만 생각하고, 공유가 되면 안 되는 부분을 해결하기 위해 synchronized를 사용합니다.

 

MainThreadExample, User1, User2는 동일하므로 Calculator만 수정합니다.

 

 

수정된 Calculator.java

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
package thread_synchronized;
 
public class Calculator {
 
    private int memory;
    
    public int getMemory() {
        return memory;
    }
    
    public synchronized void setMemory(int memory) {
        this.memory = memory;
        try {
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + ": " + this.memory);
        } catch(InterruptedException e) {
            /* 이것이 자바다 교재에서는 이 부분(catch)으로 실행이 되는 것 같지만
             * 제가 실행할 때는 정상적으로 출력이 되지 않아서
             * try 부분에 확인을 위한 코드를 삽입 하였습니다.
             */
            System.out.println(Thread.currentThread().getName() + ": " + this.memory);
        }
    }
}
 

 

Calculator.java를 위와 같이 수정해 주시고, 실행하시면 다음과 같이

 

User1: 100

User2: 50

 

동시에 실행을 하지만 공유가 되지 않은 결과가 출력되는 것을 확인하실 수 있습니다.

 

 

소개해드린 예제는 이것이 자바다에 있습니다!

동시에 실행은 해야 하지만 공유가 되면 안 되는 동기화 프로그래밍을 해야 되는 경우가 의외로 많습니다.

 

제가 진행했던 개인 프로젝트 중 Java를 사용한 간단한 Hash비교 기능과 랜섬웨어에 의하여 암호화된 파일들을 복호화 해주는 백신 아닌 백신을 제작하였었는데, 당시 빠른검사, 정밀검사 기능을 제작하던 중, GUI에서 Progress Bar를 사용하여 검사 중인 파일의 경로 + 파일명, 진행상태 표시를 위한 Progress Bar 를 실행하니 실시간으로 진행되는 파일명 출력과 게이지가 차지를 않고, 검사 종료 후, 맨 마지막에 검사한 파일의 파일명만 출력되고, 게이지 또한 공백 상태에서 꽉찬 게이지 형식으로 변경이 되었었습니다.

 

당시 pannel thread와 검사 thread 간의 임계영역 간섭 문제였던걸로 생각이 들어서 검사 thread에 synchronized를 적용하니 정상적으로 일반 백신처럼 진행되는 것을 확인 할 수 있었습니다.

 

모든 상황이 저와 같지는 않으시겠지만 굉장히 다양한 경우가 많으니 검색등을 통하여 알아보시기 바랍니다!

 

 

+ Recent posts