public static void main(String[] args) {
// Task1
for (int i = 101; i < 199; i++) {
System.out.print(i + " ");
}
System.out.print("\nTask1 Done");
// Task2
for (int i = 201; i < 299; i++) {
System.out.print(i + " ");
}
System.out.print("\nTask2 Done");
// Task3
for (int i = 301; i < 399; i++) {
System.out.print(i + " ");
}
System.out.print("\nTask3 Done");
System.out.print("\nMain Done");
}
Task2
, Task3
은 아무리 Task1
과 유사한 실행과정을 거친다고 해도, CPU는 유사성
을 인지하지 못하고, Task1
의 작업을 마칠 때 까지 대기해야 한다.유사성
을 가진 모든 수행문을 동시에 실행하도록 해준다.Thread 클래스를 확장하는 방법
class Task1 extends Thread {
public void run() {
// 작업할 내용 작성
}
}
run()
이 아닌, start()
로 호출해야 한다.Runnable 인터페이스를 실행하는 방법
class Task2 implements Runnable {
@Override
public void run() {
// 작업할 내용 작성
}
}
Thread 클래스를 확장하는 방법 및 Runnable 인터페이스를 실행하는 방법
// Thread 클래스를 확장하는 방법
class Task1 extends Thread {
public void run() {
System.out.print("Task1 Started");
for (int i = 101; i < 199; i++) {
System.out.print(i + " ");
}
System.out.print("\nTask1 Done");
}
}
// Runnable 인터페이스를 실행하는 방법
class Task2 implements Runnable {
@Override
public void run() {
System.out.print("\nTask2 Started");
for (int i = 201; i < 299; i++) {
System.out.print(i + " ");
}
System.out.print("\nTask2 Done");
}
}
public class ThreadBasicsRunner {
public static void main(String[] args) {
// Task1
System.out.print("\nTask1 Kicked Off");
Task1 task1 = new Task1();
task1.start(); // 여기서 task1.run()를 호출하면 Thread가 아닌 일반적인 메서드 호출이 되므로 병렬 처리가 되지 않는다. .start()로 호출해야 한다!
// Task2
System.out.print("\nTask2 Kicked Off");
Task2 task2 = new Task2();
Thread task2Thread = new Thread(task2);
task2Thread.start();
// Task3
System.out.print("\nTask3 Kicked Off");
for (int i = 301; i < 399; i++) {
System.out.print(i + " ");
}
System.out.print("\nTask3 Done");
System.out.print("\nMain Done");
}
}
출력 결과
Task1 Kicked Off
Task2 Kicked OffTask1 Started
Task3 Kicked Off
Task2 Started301 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 201 202 203 204 205 206 207 208 209 210 211 212 213 214 101 362 215 102 103 104 105 106 107 108 109 110 111 112 113 114 115 363 364 365 366 367 368 369 370 371 372 373 216 116 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
Task3 Done
Main Done217 218 219 220 221 222 223 224 225 226 227 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
Task1 Done228 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
Task2 Done
Task1
의 출력이 시작되기 전에 이미 Task2
의 실행이 시작되었고, 동시에 Task3
의 작업은 끝 마쳤고 이후 차례로 Task1
과 Task2
의 작업이 완료된 것을 확인할 수 있다.(병렬 처리 성공)NEW
RUNNABLE
RUNNING
BLOCKED/WAITING
TERMINATED/DEAD
Task1 Done
이 출력되었다.Thread를 실행(start)하면 RUNNABLE
, RUNNING
, BLOCKED/WAITING
중 하나의 상태를 띄게 된다.
... 214 101 362 ...
Task2의 수행인 214
출력에는 현재 Task2의 상태는 RUNNING
Task1의 수행인 101
출력 시점에는 현재 Task1의 상태는 RUNNING
RUNNABLE
상태RUNNALBE
은 실행을 하려고 하지만, 다른 Thread가 우선적으로 실행되고 있는 상태를 뜻한다.Task3의 수행인 362
는 메인 메서드에 의해 실행되어 정확히 말하면, Thread가 아닌 execution에 해당하는데 메인 메서드에 의해 실행된 것이다. 이때 다른 Thread인 Task1, Task2는 RUNNALBE
상태로 대기하고 있는다.
예) 외부 서비스의 응답을 기다리거나 데이터베이스를 이용하고 있는데, 데이터베이스가 느리다면 작업을 실행할 상황이 되지않기 때문에 외부 서비스로부터 데이터가 들어오기를 기다리고 있어 사용자는 차단당하게 된다. 사용자가 필요한 데이터를 Thread가 제공하기를 기다리는 상태가 BLOCKED
이다.
우선순위(1~10)는 추천에 불과하다. 항상 상위의 우선순위를 보장하는 것을 의미하는 것은 아니다. (힌트 느낌)
반영이 될 수도 안 될 수도 있다.
.setPriority(1~10)
// Task1
System.out.print("\nTask1 Kicked Off");
Task1 task1 = new Task1();
**task1.setPriority(1);**
task1.start();
출력 결과
Task1 Kicked Off
Task2 Kicked OffTask1 Started
Task3 Kicked Off
Task2 Started101 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 301 302 303 304 305 201 202 203 204 205 206 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 306 307 308 207 208 209 146 147 148 149 150 151 152 309 310 210 211 212 213 214 215 216 153 154 155 156 311 312 217 157 158 159 160 161 162 313 218 163 164 165 166 167 168 169 170 171 314 315 316 219 220 221 222 172 173 174 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 223 224 225 226 227 228 229 230 231 232 233 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
Task1 Done345 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 234 393 394 395 396 397 398
Task3 Done
Main Done235 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
Task2 Done
Task1 Done
이 Task2
보다 먼저 출력이 되는 것처럼 우선순위
를 부여할 수 있다.
그러나 예제를 여러 번 실행해보면 Task1
이나 Task3
가 먼저 종료 될 때도 있는 것을 확인 할 수 있다.
여기서 주의해야 할 점은 우선 순위 설정은 단지 추천 순위 정도이란 것이다. 우선 순위를 제일 높게 설정한다고 항상 먼저 실행됨을 보장 할 수는 없다.
메인 메서드인 Task3
가 실행되기 전에 Task1
과 Task2
가 실행이 완료되도록 하고 싶다.
join()
를 통해 이전 작업이 완료된 것을 보장하고 다음 작업을 수행하도록 할 수 있다.Task1
, Task2
둘 다한테 우선순위를 부여함)class Task1 extends Thread {
public void run() {
System.out.print("Task1 Started");
for (int i = 101; i < 199; i++) {
System.out.print(i + " ");
}
System.out.print("\nTask1 Done");
}
}
class Task2 implements Runnable {
@Override
public void run() {
System.out.print("\nTask2 Started");
for (int i = 201; i < 299; i++) {
System.out.print(i + " ");
}
System.out.print("\nTask2 Done");
}
}
public class ThreadBasicsRunner {
public static void main(String[] args) throws InterruptedException {
// Task1
System.out.print("\nTask1 Kicked Off");
Task1 task1 = new Task1();
task1.setPriority(1);
task1.start();
// Task2
System.out.print("\nTask2 Kicked Off");
Task2 task2 = new Task2();
Thread task2Thread = new Thread(task2);
task2Thread.setPriority(10);
task2Thread.start();
// wait for task1, task2 to complete
task1.join();
task2Thread.join();
// Task3
System.out.print("\nTask3 Kicked Off");
for (int i = 301; i < 399; i++) {
System.out.print(i + " ");
}
System.out.print("\nTask3 Done");
System.out.print("\nMain Done");
}
}
출력 결과
Task1 Kicked Off
Task2 Kicked OffTask1 Started
Task2 Started201 101 102 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 103 104 105 106 107 108 109 231 232 110 111 112 113 114 115 116 117 118 119 120 121 122 233 123 124 125 234 235 236 237 238 239 240 241 242 126 127 128 129 130 131 132 133 134 135 136 137 243 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 244 157 158 245 246 247 248 249 250 251 252 253 254 255 159 160 161 162 163 164 165 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 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 298
Task2 Done197 198
Task1 Done
Task3 Kicked Off301 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
Task3 Done
Main Done
Task1 Done
, Task2 Done
이후에 Task3
가 실행되는 것을 확인할 수 있다.sleep()
: 밀리초 동안 대기 상태에 들어간다.
yield()
: CPU 점유를 포기하고 대기 상태에 들어간다. (힌트 정도 → 반영이 안 될 수도 있음)
synchronized
: 동기화 작업을 의미
오버헤드
가 많이 생긴다.Thread를 실행했을 때 생기는 중요한 결핍 중 하나는 Thread의 실행에 대해서 그 어떤 통제도 불가능하다는 점이다.
start()
메서드를 이용하여 확립할 경우에는 매우 어렵다.또 다른 문제는 Task1, Task2 중 하나만 완료될 때까지 기다리고 싶다고 했을 때, Thread의 기본적인 기능으로는 분기 상황에 맞는 코드를 작성하는 것이 불가능하다.
Thread 생성과 실행의 중복 코드 발생
Thread가 실행한 Task의 결과를 반환받고 싶은데 Thread의 기본적인 기능으로는 불가능하다.
Executor Service
는 지금까지 말한 Thread의 불편함을 모두 해결해준다.
다수의 Thread를 한 번에 실행 가능
Thread의 상태 확인 가능
Thread의 논리 실행 가능
newSingleThreadExecutor()
: 한 번에 하나의 Thread를 실행하도록 하는 기능 제공
public class ExecutorServiceRunner {
public static void main(String[] args) {
// SingleThreadExecutor : 한 번에 하나의 Thread를 실행하도록 하는 기능 제공
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new Task1());
executorService.execute(new Thread(new Task2()));
// Task3
System.out.print("\nTask3 Kicked Off");
for (int i = 301; i < 399; i++) {
System.out.print(i + " ");
}
System.out.print("\nTask3 Done");
System.out.print("\nMain Done");
executorService.shutdown();
}
}
출력 결과
Task1 Started
Task3 Kicked Off101 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 301 302 303 304 305 306 307 130 308 131 132 309 133 310 134 311 135 136 137 138 139 140 312 141 313 142 143 314 315 316 317 318 319 320 144 145 146 147 148 149 321 322 323 324 325 326 327 328 150 151 152 153 154 155 156 157 158 159 160 329 330 331 332 333 334 335 336 337 161 162 163 164 165 166 167 168 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 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 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 186 392 393 394 395 396 397 398
Task3 Done
Main Done187 188 189 190 191 192 193 194 195 196 197 198
Task1 Done
Task2 Started201 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
Task2 Done
Task1
, Task2
는 executorService를 통해 실행하고, Task3
는 메인 메서드에서 실행이 되고 있다.SingleThreadExecutor
로 지정했기 때문에 어느 시점에서든 하나의 작업만 수행 가능하다.class Task extends Thread {
private int number;
public Task(int number) {
this.number = number;
}
public void run() {
System.out.print("\nTask" + number + " Started");
for (int i = number * 100; i <= number * 100 + 99; i++) {
System.out.print(i + " ");
}
System.out.print("\nTask" + number + " Done");
}
}
public class ExecutorServiceRunner {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.execute(new Task(1));
executorService.execute(new Task(2));
executorService.execute(new Task(3));
executorService.execute(new Task(4));
executorService.shutdown();
}
}
출력 결과
Task1 Started
Task2 Started100 200 201 202 101 203 204 205 206 207 208 102 103 104 105 106 107 209 210 211 212 213 108 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 109 110 258 111 112 113 114 115 116 117 118 119 259 260 261 262 263 264 120 121 122 123 124 125 265 126 127 128 129 130 131 132 133 266 267 268 269 270 134 135 136 271 272 273 137 138 274 275 276 277 278 279 280 281 282 283 139 140 141 142 143 144 145 146 147 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
Task2 Done
Task3 Started300 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 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 Done
Task4 Started400 401 402 403 359 360 361 362 363 364 404 405 365 406 407 408 409 410 411 412 413 414 415 416 417 366 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 367 368 369 370 371 372 373 374 375 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 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 Done480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499
Task4 Done
Task1
과 Task2
2가지 모두 실행되었고, 이후 Task2
가 실행 완료됨 동시에 Task3
실행 시작, 이어서 Task1
의 실행 완료됨 동시에 Task4
의 실행이 시작된 것을 확인할 수 있다.Thread로부터 값을 반환받기
class CallableTask implements Callable<String> {
private String name;
public CallableTask(String name) {
this.name = name;
}
@Override
public String call() throws Exception {
Thread.sleep(1000); // 일련의 과정을 sleep으로 대체
return "Hello " + name;
}
}
public class CallableRunner {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(1);
Future<String> welcomeFuture = executorService.submit(new CallableTask("in28Minutes"));
System.out.print("\n new CallableTask(\"in28Minutes\") executed");
String welcomeMessage = welcomeFuture.get();
// 해당 줄 부터는 get() 실행이 완료될 때까지 대기 상태에 머물러 있다.
System.out.print("\n" + welcomeMessage);
System.out.print("\n Main completed");
executorService.shutdown();
}
}
Future
라는 객체를 통해서 반환되므로 꼭 .get()
를 통해 값을 가져와야 한다.출력 결과
new CallableTask("in28Minutes") executed
Hello in28Minutes
Main completed
모든 Task의 실행을 완료할 때까지 대기하고 한 번에 출력하기
class CallableTask implements Callable<String> {
private String name;
public CallableTask(String name) {
this.name = name;
}
@Override
public String call() throws Exception {
Thread.sleep(1000); // 일련의 과정을 sleep으로 대체
return "Hello " + name;
}
}
public class MultipleCallableRunner {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newFixedThreadPool(3);
List<CallableTask> tasks = List.of(
new CallableTask("in28Minutes"),
new CallableTask("Ranga"),
new CallableTask("Adam")
);
List<Future<String>> results = executorService.invokeAll(tasks);
for (Future<String> result : results) {
System.out.println(result.get());
}
executorService.shutdown();
}
}
출력 결과
Hello in28Minutes
Hello Ranga
Hello Adam
1
→3
으로 변경하면 조금 더 빠르게 동시에 출력이 되는 것을 확인할 수 있다.Task중 하나가 끌날 때까지만 기다려서 실행이 첫 번째로 끝난 것의 값을 결과로 얻고 싶은 경우
...
public class MultipleAnyCallableRunner {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newFixedThreadPool(1);
List<CallableTask> tasks = List.of(new CallableTask("in28Minutes"),
new CallableTask("Ranga"),
new CallableTask("Adam"));
String result = executorService.invokeAny(tasks);
System.out.println(result);
executorService.shutdown();
}
}
출력 결과
Hello Ranga
3
개이기 때문에 실행할 때마다 다른 결과가 나오는 것이 가능하다1
개로 지정하면 같은 결과만 나올 것이다. (Thread 수가 곧 사용자 수이다.)멀티 스레딩의 흐름제어에 대해서 알 수 있었으며, Thread 제어에 있어서 ExecutorService의 용이성에 대해서 알 수 있었던 좋은 경험이었다. Task 작업 속도를 증가시키고, 병렬 구조로 가능한 많은 Task를 실행하고자 할 때 멀티 스레딩을 고려해보자
와아! 이렇게 정리하신다니! 배우고 갑니다^^