◇ 쓰레드 상태
- 쓰레드 상태 : 실행과 대기를 반복하며 run()메서드를 수행, 메서드가 종료되면 실행을 멈춤
- 일시정지 : 일시정지는 쓰레드가 실행 할 수 없는 상태, 다시 실행하기 위해서는 다시 실행대기로 넘어가야함
- 쓰레드 상태 정리
상태 Enum 설명 객체생성 NEW 쓰레드 객체 생성, start()메서드 호출 전 실행대기 RUNNABLE 실행 상태로 언제든지 갈 수 있는 상태 일시정지 WAITING 다른 쓰레드가 notify할 때까지 기다리는 상태 일시정지 TIMED_WATING 주어진 시간동안 기다리는 상태 일시정지 BLOCKED 사용하고자 하는 객체의 Lock이 풀릴 때까지 기다리는 상태 종료 TERMINATED 쓰레드 작업이 종료된 상태
◇ 쓰레드 제어
- sleep() : 현재 쓰레드를 지정된 시간동안 멈추게 한다.
※sleep()는 쓰레드 자기 자신에 대해서만 멈출 수 있다.
- 사용방법try { Thread.sleep(2000); // 2초 } catch (InterruptedException e) { e.printStackTrace(); }
→ Thread.slee(ms) 밀리초 단위로 설정
→ 예외처리를 해야함, sleep상태에서 interrupt()를 만나면 다시 실행되어 InterruptedException이 발생할 수 있기떄문→ 특정 쓰레드를 지목해서 멈추게 하는 것은 불가능(static메서드이기때문) - interrupt() : 일시정지 상태인 쓰레드를 실행대기 상태로 만듭니다.
- 위 코드에서 thread.interrupt();가 실행되면 sleep을 깨워public class Main { public static void main(String[] args) { Runnable task = () -> { try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("task : " + Thread.currentThread().getName()); }; Thread thread = new Thread(task, "Thread"); thread.start(); thread.interrupt(); System.out.println("thread.isInterrupted() = " + thread.isInterrupted()); } }
task에서 catch문으로 들어가 sleep아래 코드가 실행되지않는다.
위와 같이 while문을 통해 상태를 체크하면 오류를 방지할 수 있다. Runnable task = () -> { while (!Thread.currentThread().isInterrupted()) { try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName()); } catch (InterruptedException e) { break; } } System.out.println("task : " + Thread.currentThread().getName()); };
- join() : 정해진 시간동안 지정한 쓰레드가 작업하는 것을 기다린다.
※ 시간을 지정하지 않았을 때는 지정한 쓰레드의 작업이 끝날 때까지 기다린다.
Thread thread = new Thread(task, "thread"); thread.start(); try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); }
- yield() : 남은 시간을 다음 쓰레드에게 양보하고 자신은 실행 대기 상태가 된다.
- 실행되면 task에 의해 각 쓰레드가 자신의 이름과 함께 찍히게 된다.public class Main { public static void main(String[] args) { Runnable task = () -> { try { for (int i = 0; i < 10; i++) { Thread.sleep(1000); System.out.println(Thread.currentThread().getName()); } } catch (InterruptedException e) { Thread.yield(); } }; Thread thread1 = new Thread(task, "thread1"); Thread thread2 = new Thread(task, "thread2"); thread1.start(); thread2.start(); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } thread1.interrupt(); } }
- sleep(5000)에 의해 5초 후 thread1.interrupt()가 실행된다.
- catch문에 의해 thread1은 yield()가 실행되고 남은 시간동안 thread2만 동작하게된다. - synchronized : 한 쓰레드가 진행중인 작업을 다른 쓰레드가 침범하지 못하도록 막는 것
(임계영역을 설정하여 Lock을 가진 하나의 쓰레드만 출입이 가능)
※ 멀티 쓰레드의 경우 여러 쓰레드가 한 프로세스의 자원을 공유해서 작업하기 때문에 서로에게 영향을 줄 수 있다.
- 메서드 전체를 임계영역으로 지정
- 특정 영역을 임계영역으로 지정public synchronized void asyncSum() { ...침범을 막아야하는 코드... }
synchronized(해당 객체의 참조변수) { ...침범을 막아야하는 코드... }
- 예제
public class Main { public static void main(String[] args) { AppleStore appleStore = new AppleStore(); Runnable task = () -> { while (appleStore.getStoredApple() > 0) { appleStore.eatApple(); System.out.println("남은 사과의 수 = " + appleStore.getStoredApple()); } }; for (int i = 0; i < 3; i++) { new Thread(task).start(); } } } class AppleStore { private int storedApple = 10; public int getStoredApple() { return storedApple; } public void eatApple() { if (storedApple > 0) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } storedApple -= 1; } } }
다음 예제를 실행하게되면
- for문을 통해 3개의 쓰레드가 동시에 NEW하게되고 start()하게된다.
- 각 쓰레드들은 eatApple을 수행하게되는데 만약 남은 사과가[9, 8, 7], [6, 5, 4], [3, 2, 1] 이렇게 줄어들다가
- 1이 남은상황에서 task의 while문에 진입하게 되면 각 쓰레드들이 진입 당시에는 1개가 남아있는 상황이다.
- eatApple의 if문에서도 아직 사과는 1개가 남아있기 떄문에 try문으로 진입하게되고
- 마지막 실행이 되면 남은 사과 출력은[0, -1, -2]와 같은 상황이 발생하게된다.
eatApple()메서드를 다음과 같이 synchornized 하게 되면 해결 할 수 있다.
public void eatApple() { synchronized (this) {if문}
- wait() : synchornized()를 수행하다가 작업을 더이상 진행할 상황이 아니면 쓰레드가 Lock을 반납하고 기다리게 함
- notify() : 추후에 작업을 진행할 수 있는 상황이 되면 notify()호출
- Lock
- ReentrantLock : 재진입 가능한 일반적인 베타 Lock,
특정 조건에서 Lock을 풀고, 나중에 다시 Lock를 얻어 임계 영역으로 진입이 가능 - ReentrantReadWriteLock : 읽기, 쓰기 Lock을 따로 제공
읽기에는 공유적이고, 쓰기에는 베타적인 Lock
읽기 Lock이 걸려있으면 다른 쓰레드들도 읽기 Lock을 중복으로 걸고 읽기 수행가능
읽기 Lock이 걸려있는 상태에서 쓰기 Lock을 거는것은 불가(데이터 변경 방지) - StampedLock : 데이터를 변경하기전에 Lock를 걸지않는 것
- ReentrantLock : 재진입 가능한 일반적인 베타 Lock,
- Condition : 특정 조건이 맞는 쓰레드를 깨우는 기능(notify()는 쓰레드 구분을 할 수 없다)
- await() : 특정 지점에 wati()
- signal() : 특정 지점에 notify()
'Java & Spring > Java' 카테고리의 다른 글
csv파일으로 더미데이터 생성하기 (0) | 2024.11.08 |
---|---|
14일차 - Java강의(쓰레드) (0) | 2024.08.01 |
13일차 - Java강의(예외, 제네릭) (0) | 2024.07.31 |
11일차 - Java강의(계산기 실습, 클래스화, 추상화) (0) | 2024.07.29 |
10일차 - Java강의(제어자, package, import, 상속, this, super) (0) | 2024.07.26 |