티스토리 뷰
1. Thread.join()
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 JoinThreadMain {
public static void main(String[] args) {
//Task1
Task1 task1 = new Task1();
task1.start();
//Task2
Task2 task2 = new Task2();
Thread task2Thread = new Thread(task2);
task2Thread.start();
//Task3
for(int i = 301; i <= 399; i++) {
System.out.print(i + " ");
}
System.out.println("\\nTask3 Finish");
}
}
위 코드는 각각의 스레드에서 별도로 Task1~2가 수행되는 코드입니다. 그렇기 때문에 각각의 Task의 수행이 마치는 것을 기다리지 않고 병렬로 실행되게 됩니다. 근데 여기서 Task1의 수행을 먼저 마치고 Task2, Task3의 코드를 처리해야 하거나, Task2의 실행 결과값을 Task1,3에서 사용해야 할 때는 어떻게 해야 할까요?
Join() 메소드 예시
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 JoinThreadMain {
public static void main(String[] args) throws InterruptedException {
//Task1
Task1 task1 = new Task1();
task1.start();
//Task2
Task2 task2 = new Task2();
Thread task2Thread = new Thread(task2);
task2Thread.start();
// task2의 실행이 종료 될 때까지 Task3를 실행하지 않고 대기함
task2Thread.join();
//task1.join();
//Task3
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 126 127 128 129 130 131 132 133 134 135
Task2 Start
136 137 201 138 202 139 203 140 204 141 205 142 206 143 207 144 145 146 208 147 209 148 210 149 211 150 212 151 213 152 214 153 215 154 216 155 217 156 218 157 219 158 220 159 221 160 222 161 223 162 224 163 225 164 226 227 228 229 230 231 232 233 234 235 236 165 237 166 238 167 239 168 240 169 241 170 242 243 244 245 246 247 171 248 172 249 173 250 174 251 175 252 176 253 177 254 178 255 179 256 180 257 181 258 182 259 183 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 184 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 185
Task2 Finish
186 187 188 301 189 302 190 303 191 304 305 192 306 193 194 195 196 307 197 308 198 309 199 310
Task1 Finish
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
예시와 같이 task2Thread.join(); 뒤에 .join()을 붙여주고 InterruptedException에 대한 예외 처리 부분을 추가해주면 됩니다. 그러면 Task2의 수행이 마치기 전까지는 Task3가 실행이 안되는 것을 확인할 수 있습니다. Task1도 적용하려면 마찬가지로 task1.join();를 추가해주면 task1과 task2가 병렬 실행되고 코드 수행이 종료되면 Task3가 실행되는 것을 확인할 수 있습니다.
2. Thread.sleep()
정적 메소드에 해당되며, Thread.sleep(밀리세컨드) 형식으로 작성해주고 InterruptedException 에 대한 예외 처리를 해주면 됩니다. 예를 들어 Thread.sleep(10000)이라고 적으면, 10초동안 해당 스레드는 대기 상태가 됩니다.
public class Task1ThreadMain {
public static void main(String[] args) throws InterruptedException {
Task1 task1 = new Task1();
task1.start();
for(int i = 201; i <= 299; i++) {
System.out.print(i + " ");
}
System.out.println("\\nTask2 Finish");
// 10초 동안 대기
Thread.sleep(10000);
for(int i = 301; i <= 399; i++) {
System.out.print(i + " ");
}
System.out.println("\\nTask3 Finish");
}
}
3. Thread.yield()
Thread.sleep() 메서드와 비슷한 기능을 합니다. 다량의 CPU가 주어졌는데 이 CPU를 원하지 않는 경우라고 할때. yield()를 선언하면, 현재의 thread가 현재의 이용 가능한 이 상태를 양보하거나 양도할 의사가 있지만 가능한 빨리 다시 예약 되기를 원하다는 것을 스케쥴러에 알립니다. 당연하게도 스케줄러는(한정된 메모리를 여러 프로세스가 효율적으로 사용할 수 있도록 실행할 프로세스를 선택하는것) 이러한 정보를 무시할 수 있으며 운영 체제에 따라 다양한 동작을 하게됩니다.
class Task1 extends Thread {
@Override
public void run() {
System.out.println("\\nTask1 Start");
for(int i = 101; i <= 199; i++) {
System.out.print(i + " ");
}
Thread.yield();
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");
}
}
이 Thread에서 Task1은 스케줄러에게 ‘충분하게 주목받았고, CPU가 충분하게 있으니, CPU를 포기하겠다’라고 말합니다. 하지만 이에 대해 스케줄러가 다음과 같이 답할 수 있습니다. ‘아니야, CPU를 가지고 있어야 하니 양도하지 말아줘' 라고 답할 수도 있습니다. 그래서 이게 그저 정보에 해당한다고 앞에서 말했던것입니다.
공식 문서에 따르면 yield()를 사용할 필요가 거의 없으므로 목적이 명확하지 않으면 피해야 합니다.
그럼에도 불구하고 yield()는 동시성 제어 구조 설계, 컴퓨팅 집약적인 프로그램에서 시스템 응답성 개선 등에 사용됩니다. 그러나 원하는 결과를 보장하기 위해서 상세한 프로파일링 및 벤치마킹이 수반되어야 합니다.
4. synchronized
이러한 정적 메서드 중에서 Thread를 다룰 때 매우 중요한 키워드가 하나 있습니다. 바로 동기화 작업을 의미하는 synchronized 입니다.
HashTable에서도 사용되고있는데, HashTable은 자바 1에서부터 사용 가능하고, 이런 식으로 동기화에 접근하는 방법 또한 자바1에서부터 가능해 왔다는 것을 알 수 있습니다.
HashTabel 내부의 코드를 보면, 대부분의 메소드가 동기화(synchronized) 키워드를 가지고 있다는 것을 확인할 수 있습니다. 그러므로 HashTable에서의 거의 모든 메소드는 동기화된 키워드, 즉 synchronized keyword를 가집니다.
그렇다면 여러분들은 지금 왜 HashTable이 동기화되어야 하는 건지에 대한 의문을 가지고 있으리라 생각합니다.
아 두 가지의 Thread 사이에서 공유되는 데이터가 존재한다고 가정했을때, 여기서는 HashTblae을 공유 하고 있다고 하겠습니다. 여기에 synchronized를 적어주면, Task1과 Task2 둘 중 하나의 Thread만 동기화된 메소드 안의 코드를 실행할 수 있죠.
예를들어, 만약 특정 시점에서 동기화된 메서드가 50개 있다고 해보겠습니다. 그리고 이 중 하나의 Thread만 이 동기화된 50개의 메소드 중 아무 메소드를 실행시킬 수 있습니다. 50개의 동기화가 끝난 메서드가 있습니다. 그리고 여기서 동기화된 코드가 1000줄이 있고, 이 중 단 한개의 Thread만이 이 1000줄의 코드를 실행할 수 있습니다. 만약 이미 하나의 Thread가 1000줄의 코드 중 어떤 코드이든지 실행하고 있다면, 다른 Thread는 첫 번째 Thread가 동기화 코드에 대한 실행이 완료될 때까지 대기하다가 메소드를 실행하기 위한 접근 권한을 얻기 전에 나와주어야 합니다. 이게 sysnchronized keyworkd의 중요성이라고 할 수 있습니다.
동기화에서 이해하고 넘어가야 하는 정말 중요한 것은 동기화가 overhaed가 많이 생긴다는 점입니다. 이유는 이 1000줄의 코드가 단 한개의 Thread에 대해서만 실행이 가능하므로 다른 코드를 실행해야 하는 Thread 들은 무조건 대기를 해야 하기 때문입니다. 그러므로 시스템의 작동에 영향이 생길 확률이 높습니다. 자바의 최신 버전에서는 Thread를 더 안정성 있게 사용이 가능하도록 하는 대안들이 많이 있습니다. 동기화를 사용해 접근하는 방법에만 의존하지 않는다는 이야기 입니다. concurrent Collection이 그에 대한 예로 볼 수 있습니다.
참고
'[JAVA]' 카테고리의 다른 글
[Java] switch 표현식 (0) | 2023.03.21 |
---|---|
[Java] 자바 스레드와 동시성(5) - Thread Pool (스레드풀), Executor (1) | 2022.10.10 |
[Java] 자바 스레드와 동시성(3) - 우선 순위 (0) | 2022.10.01 |
[Java] 자바 스레드와 동시성(2) - 메인 스레드 (0) | 2022.09.29 |
[Jave] 자바 스레드와 동시성(1) - 스레드 적용 방법, 상태 (0) | 2022.09.29 |
- Total
- Today
- Yesterday
- java11
- chmod
- 한글깨짐
- JUnit
- Thread
- thread priority
- JPA
- codepoint
- aspectj
- spring-security
- 파스칼 표기법
- java
- Redis
- jdk13
- gradle
- JAVA8
- 확인창
- jdk12
- sgw
- 카멜 표기법
- ThreadPool
- junit5
- JetBrains Mono
- Mockito
- Visual Studio 2022
- spring
- hot-deploy
- IntelliJ
- Executor
- Jenkins
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |