티스토리 뷰

반응형

1. Thread의 필요성

public class BasicMain {
    public static void main(String[] args) {
        for(int i = 101; i <= 199; i++) {
            System.out.print(i + " ");
        }
        System.out.println("\\nTask1 Finish");

        for(int i = 201; i <= 299; i++) {
            System.out.print(i + " ");
        }
        System.out.println("\\nTask2 Finish");

        for(int i = 301; i <= 399; i++) {
            System.out.print(i + " ");
        }
        System.out.println("\\nTask3 Finish");
        System.out.println("\\nMain Finish");
    }
}
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 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 
Task1 Finish
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 
Task2 Finish
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 
Task3 Finish

기본적으로 프로그래밍 코드를 작성하게 될 때 위와 같이 작성하게 됩니다.(Task1이 끝나면 Task2가 시작되고,, 순차적으로 실행되는 구조)

여기서의 Task1, Task2, Task3는 각각 독립적으로 실행됩니다. (그 밖에도 몇몇 Task는 외부의 실행에 의해 의존적으로 실행되기도 합니다). task의 실행 단계에서 사용자의 CPU는 충분히 활용되지 못할 것입니다. 수행해야할 Task는 세개나 있지만 CPU는 실행 할 task를 한가지 씩만 처리하게 됩니다. Task1이 실행이 완료될때까지 Task2,3은 계속 기다리거나 대기할 것입니다. Task2가 아무리 Task1과 유사한 실행 과정을 거친다고 해도 CPU는 이 유사성을 인지하지 못합니다.

Thread는 이렇게 유사성을 가진 모든 수행문을 동시에 실행하도록 해줍니다. 이 thread를 실행하는 task1 코드를 하나 작성하고, 또 다른 thread로 task2/3 코드를 작성하게 된다면, 각각의 쓰레드에서 세가지 task가 모두 실행됩니다. 이로써 CPU의 효율성이 향상되는데, 이유는 외부 서비스나 데이터 저장소로부터 데이터 입력을 기다리며 지속해서 다른 task에 대한 정보를 얻을 수 있기 때문(두 가지 이상의 작업을 병렬 처리 할 수 있음)입니다.

2. Thread 적용하기

extends Thread

class Task1 extends Thread {
    @Override
    public void run() {
        for(int i = 101; i <= 199; i++) {
            System.out.print(" " + i);
        }
        System.out.println("\\nTask1 Finish");
    }
}

Thread를 extend 해주고 run 메서드를 구현해줍니다. 여기서 run 메서드는 override를 해주고 있으므로 메서드의 signature는 예제와 동일해야 합니다.

해당 코드를 실행시켜주는 코드는 아래와 같이 작성해주면 됩니다. 이때 주의해야할점은 run 메서드를 실행하지 않는 점입니다. run() 호출 시에는 병렬 처리 되지 않으므로 주의해줘야합니다.

Task1 task1 = new Task1();
task1.start();
class Task1 extends Thread {
    @Override
    public void run() {

        for(int i = 101; i <= 199; i++) {
            System.out.print(i + " ");
        }
        System.out.println("\\nTask1 Finish");
    }
}

public class Task1ThreadMain {
    public static void main(String[] args) {
        Task1 task1 = new Task1();
        task1.start();

        for(int i = 201; i <= 299; i++) {
            System.out.print(i + " ");
        }
        System.out.println("\\nTask2 Finish");

        for(int i = 301; i <= 399; i++) {
            System.out.print(i + " ");
        }
        System.out.println("\\nTask3 Finish");
    }
}
201 202 203 
Task1 Start
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 101 102 222 103 104 105 223 106 107 108 109 224 110 111 112 113 225 114 226 115 227 116 228 117 229 118 230 119 231 120 232 121 233 122 234 123 235 236 124 237 125 238 126 239 127 240 128 241 129 242 130 243 244 245 246 247 248 131 132 133 134 135 136 249 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 250 166 167 168 169 170 251 171 252 172 253 173 254 174 255 175 176 177 256 178 257 179 258 180 259 181 260 261 182 183 262 184 263 185 264 265 266 267 186 268 269 270 271 272 273 274 275 187 276 188 277 189 190 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 191 296 297 298 299 192 
Task2 Finish
193 301 302 194 195 303 196 197 198 199 304 
Task1 Finish
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 
Task3 Finish

Task1의 수행이 끝나기도 전(Task1 Finish)에 Task2가 함께 수행되는 것을 확인했습니다. Thead를 상속 받아 구현 받으면 병렬로 코드가 작동하게 됩니다.

implements Runnable

class Task2 implements Runnable {
    @Override
    public void run() {
        System.out.println("\\nTask2 Start");
        for(int i = 201; i <= 299; i++) {
            System.out.print(i + " ");
        }
        System.out.println("\\nTask2 Finish");
    }
}

Runnable를 implements 해주고 run 메서드를 구현해줍니다. Thread와 마찬가지로 run 메서드는 override를 해주고 있으므로 메서드의 signature는 예제와 동일해야 합니다.

해당 코드를 실행시켜주는 코드는 아래와 같이 작성해주면 됩니다.

Task2 task2 = new Task2();
Thread task2Thread = new Thread(task2);
task2Thread.start();
class Task1 extends Thread {
    @Override
    public void run() {
        System.out.println("\\nTask1 Start");
        for(int i = 101; i <= 199; i++) {
            System.out.print(i + " ");
        }
        System.out.println("\\nTask1 Finish");
    }
}

class Task2 implements Runnable {
    @Override
    public void run() {
        System.out.println("\\nTask2 Start");
        for(int i = 201; i <= 299; i++) {
            System.out.print(i + " ");
        }
        System.out.println("\\nTask2 Finish");
    }
}

public class Task2ThreadMain {
    public static void main(String[] args) {
        Task1 task1 = new Task1();
        task1.start();

        Task2 task2 = new Task2();
        Thread task2Thread = new Thread(task2);
        task2Thread.start();

        for(int i = 301; i <= 399; i++) {
            System.out.print(i + " ");
        }
        System.out.println("\\nTask3 Finish");
    }
}
Task1 Start
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 301 302 303 304 305 306 307 308 126 127 128 129 130 131 132 133 134 135 309 310 311 312 313 314 315 316 317 136 137 138 139 140 
Task2 Start
201 202 203 141 142 143 144 145 146 147 148 149 150 151 318 319 320 321 322 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 204 205 206 207 208 209 171 172 173 174 175 176 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 177 178 179 180 181 182 183 210 211 212 213 214 215 216 217 218 219 220 221 222 223 184 185 186 187 188 189 340 341 342 343 344 345 346 347 190 191 192 193 194 195 196 197 198 199 
Task1 Finish
224 225 226 227 228 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 229 230 231 232 233 364 365 366 367 368 369 370 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 371 372 373 374 375 376 377 378 379 380 260 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 261 262 263 264 265 266 267 268 269 270 
Task3 Finish
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 
Task2 Finish

로그를 확인해보면 Task1~3가 동시에 수행(38 339 177 178 179 180 181 182 183 210 211 )되고 있는 것을 확인 할 수 있습니다. Thread를 사용하면 Task1이나 2,3의 처리 중간 중간에 대기하는 중에도 계속 다른 일을 처리하며 놀거나 비는 시간 없이 지속적으로 코드를 진행하는 것이 가능합니다.

3. Thread의 상태

스레드는 주어진 시점에서 오직 아래의 상태 중 하나만 가지고 있을 수 있습니다.

   
상태 설명
NEW 새 스레드가 생성되면 NEW 상태가 됩니다.
스레드가 NEW 상태에 있을 때 스레드 내 코드는 아직 실행(run)되지 않았으며 실행을 시작(start)하지도 않았습니다
RUNNABLE 실행할 준비가 된 스레드가 실행 가능 상태로 이동됩니다.
이 상태에서 스레드는 실제로 실행 중이거나 언제든지 실행할 준비가 되어 있을 수 있습니다. (스레드 스케줄러에서 스레드의 실행을 관리 합니다.)
BLOCKED/WAITING 다중 스레드 프로그램은 각 개별 스레드에 고정된 시간을 할당합니다.
각각의 모든 스레드는 잠시 동안 실행된 다음 다른 스레드가 실행할 수 있도록 CPU를 일시 중지하고 다른 스레드에 양도합니다.
이 경우 실행 준비가 된 모든 스레드와 CPU를 기다리는 현재 실행 중인 스레드는 RUNNABLE 상태가 됩니다.
BLOCKED monitor lock을 기다리는 blocked 스레드.
blocked 상태의 스레드는 monitor lock이 동기화 된 블록/메서드에 들어가거나 Object.wait()를 호출한 후 동기화 된 블록/메서드에 다시 들어가는 것을 대기하고 있습니다.
WAITING 대기중인 상태. 다음 메서드 중 하나를 호출하면 waiting 상태가 됩니다.
 - Object.wait with no timeout
 - Thread.join with no timeout
 - LockSupport.park
TIMED_WAITING 스레드는 time-out parameter가 있는 메서드를 호출할 때 TIMED_WAITING 상태에 있습니다.
스레드는 시간 초과되어 완료되거나 알림이 수신 될 때까지 이 상태에 있습니다.
예를 들어 스레드가 sleep() 또는 조건부 wait()를 호출하면 TIMED_WAITING 상태로 변경됩니다
다음 메서드 중 하나를 호출하면 time_waiting 상태가 됩니다.
 - Thread.sleep
 - Object.wait with timeout 
 - Thread.join with timeout
 - LockSupport.parkNanos
 - LockSupport.parkUntil
TERMINATED 스레드의 코드가 프로그램에 의해 완전히 실행되어 정상적으로 종료된 상태 또는, segmentation fault 또는 unhandled exception과 같은 비정상적인 오류 이벤트가 발생되어 스레드가 종료된 상태
package com.study.thread;

// Java program to demonstrate thread states
class thread implements Runnable {
    public void run() {
        // moving thread2 to timed waiting state. 
                // 타임아웃 설정을 안한 join() 호출로 thread1의 수행이 종료 될때까지 thread2 wait 상태. 
                // join(1)로 타임 아웃 설정을 해준다면 thread2는 timed_waiting 상태
        try {
            Thread.sleep(1500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("State of thread1 while it called join() method on thread2 -" + ThreadStateMain.thread1.getState());
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ThreadStateMain implements Runnable {
    public static Thread thread1;
    public static ThreadStateMain obj;

    public static void main(String[] args) {
        obj = new ThreadStateMain();
        thread1 = new Thread(obj);

        // 스레드 생성만 하고 실행은 하지 않음. thread1은 new 상태
        System.out.println("State of thread1 after creating it - " + thread1.getState());
        thread1.start();

        // 실행중인 스레드. thread2는 runabble 상태
        System.out.println("State of thread1 after calling .start() method on it - " + thread1.getState());
    }

    public void run() {
        thread myThread = new thread();
        Thread thread2 = new Thread(myThread);

        // 스레드 생성만 하고 실행은 하지 않음. thread2는 new 상태
        System.out.println("State of thread2 after creating it - " + thread2.getState());
        thread2.start();

        // 스레드를 실행함. thread2의 상태가 runabble로 변경됨.
        System.out.println("State of thread2 after calling .start() method on it - " + thread2.getState());

        try {
            // thead1에서의 sleep() 호출로 thread1은 timed-wait 상태
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("State of thread2 after calling .sleep() method on it - " + thread2.getState());

        try {
            // thread2의 상태가 terminated가 될때까지 thread1은 waiting 상태.
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("State of thread2 when it has finished it's execution - " + thread2.getState());
    }
}
State of thread1 after creating it - NEW
State of thread1 after calling .start() method on it - RUNNABLE
State of thread2 after creating it - NEW
State of thread2 after calling .start() method on it - RUNNABLE
State of thread2 after calling .sleep() method on it - TIMED_WAITING
State of thread1 while it called join() method on thread2 -WAITING
State of thread2 when it has finished it's execution - TERMINATED

새 스레드가 생성되면 해당 스레드는 NEW 상태가 됩니다. 스레드에서 start() 메서드가 호출되면 스레드 스케줄러가 해당 메서드를 RUNNABLE 상태로 이동합니다. 스레드 인스턴스에서 join() 메서드(no timeout)가 호출될 때마다 해당 명령문을 실행하는 현재 스레드는 이 스레드가 TERMINATED 상태로 이동할 때까지 기다립니다. 따라서 최종 명령문이 콘솔에 인쇄되기 전에 프로그램은 thread2에서 join()을 호출하여 thread2가 실행을 완료하고 TERMINATED 상태로 이동하는 동안 thread1이 대기하도록 합니다. thread1은 thread2에서 join()을 호출하여 thread2가 실행을 완료하기를 기다리고 있기 때문에 WAITING 상태가 됩니다.


반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2025/02   »
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
글 보관함