멀티스레드프로그래밍1 : 개념, 용어정리, 스래드 상태와 제어
개념, 용어정리
용어 | 개념 |
---|---|
애플리케이션 | 이클립스나 파워포인트, 브라우저와 같은 코드 덩어리 |
프로세스 | 설치된 애플리케이션을 실행하게되면 운영체제(OS)로부더 메모리의 일정영역을 할당받고 CPU와 HDD를 이용해서 동작하는 것 |
멀티프로세스 | 동시에 여러 프로세스를 실행 |
스레드 | 프로세스 동작의 최소 단위. 모든 프로세스는 하나 이상의 스레드로 구성 |
메인스레드 | 메인스레드가 main()메서드를 실행하면서 애플리케이션이 구동됨 |
멀티스레드 | 둘 이상의 스레드로 구성된 프로세스 |
멀티스레드 프로그램 | 작업을 스레드 단위로 분리해서 병렬 수행가능 |
- 스레드는 모든 게임에 자주 사용된다
- 총게임에서 총알을 발생하기 등등
멀티스레드 장단점
장점
- CUP사용률 향상
- 작업의 분리로 응답성 향상
- 자원의 공유를 통한 효율성 증대
단점
- 컨텍스트 스위칭 과정에 별도의 비용발생
- 제어가 어려움 : 어떤 스레드를 먼저 실행할지 제어하기 힘들다
- 동기화(synchronization), 교착상태(deadlock)와 같은 문제 발생
스레드 생성
2가지 생성방법
- Runnable 인터페이스를 구현하는 방법 : 오버라이딩 -> 생성자에 파라미터를 넘겨주면서 객체생성
- Thread클래스를 상속받는 방법 : implements 끝
다음 예시코드를 보자. 아래는 -
를 5번반복하고 @
는 10번 반복후 출력하는 코드이다.
1 | public static void main(String[] args) { |
예시코드의 의문점 3가지
위의 코드에 3가지 의문점이 있다.
- run()이 없는데 실행되었다 : start()가 호출되면 JVM이 운영체제의 스레드 스케줄러에 의해 가능할때 run()을 호출한다.
- 출력값이 매번다르다 : JVM스케쥴러가 달라져서.
메인끝
은 맨 마지막에 작성했는데 제일 처음 출력되었다 : main()스레드가 t1의 start()스레드를 생성
하게되면 별도의 스택공간을 구성한다. 이 공간은 메인스레드공간과 전혀 무관한 t1스레드의 공간으로 별도의 흐름이 생겨난다. 이후 두 스레드는 병렬스레드로 번갈아가면서 작업이 되는데 start()스레드 전에 main()스레드가 종료되었기때문에 제일 먼저 출력되었다.
만약 위의 코드에서 start()가 아닌 run()을 하면 어떻게 될까?
1 | t1.run(); |
- 이때는 메인스레드에서 실행되는 것이고 t1과t2가 별도의 스택을 구성하지않기때문에 싱글스레드이므로 코드 순서대로 실행된다.
어떤 스레드가 먼저 실행될까?
스레드들은 별도이 스택에서 따로따로 동작하기 때문에 제어가 어렵다.
1 우선순위를 통한 제어
스레드가 runnable상태에서 선택될 때는 우선순위가 적용됨.
MIN_PRIORITY = 1, MAX_PRIORITY = 10이다. 노말은 5.
아래예시는 F에 우선순위 10을 주고 T에는 제일 낮은 우선순위를 준 뒤 출력하는 값이다
1 | static class MessengerThread extends Thread{ |
우선순위때문에 F가 모두 출력된뒤 T가 출력되어야하지만 출력값은 그게 아니다.
왜그럴까?
스레드의 우선순위 역할은 실행할 확률이 높아지는 것뿐 우선순위만으로는 100%제어할 수 없다.
2 sleep()을 통한 상태제어
- sleep()메서드는 동작하는 스레드를 주어진 시간동안 일시정지키셔 대기 풀에서 sleep하게 한다.
- 대기시간이 끝나거나 interrupt()메서드가 호출되면 대기 풀에서 벗어나 다시 runnable상태로 이동한다.
3 interrupt()를 통한 상태제어
- interrupt()를 사용하여 대기 중인 스레드에게 InterruptedException을 발생시켜 즉시 runnable상태로 이동한다.
- sleep()이나 join()으로 인해 대기 풀에 대기 중인 스레드들은 지정된 시간이 지나거나 끼웠던 스레드가 종료되면 자동으로 runnable 상태로 이동한다.
- BUT 대기 중인 스레드가 임의로 runnable상태로 바꿔야할 때가 있다
- 예를 들어 음악 재생에서 일시정지했다가 다시 실행하는 경우가 해당.
- 예시 : 카운트10초동안 구구단 문제를 풀고 답을 입력하면 카운드가 종료되면서 사용자가 기입한 답과 정답을 리턴하는 코드이다
1 | public static void main(String[] args) { |
4 join()를 통한 상태제어
- sleep()과 비슷하게 스레드를 대기 풀로 이동시키는 메서드
- 다른 스레드의 작업이 종료될때까지 join()을 호출하는 스레드는 대기풀에서 대기
- 다른 작업을 나의 작업에 참여(join)시킨다.
- 예시 : 구구단만들기
메인스레드와 9개의 구구단 스레드, 숫자를 곱할 때마다 1초씩 sleep()을 호출
메인스레드는 구구단 스레드들이 각 단을 완성하면 최종 결과를 출력
1 | static class GuguThread extends Thread{ |
5 yield()를 통한 상태제어
- sleep()이나 join()은 스레드 상태를 대기상태로 변경하는 반면 yield()는 대기상태로 변하지 않고 동일한 우선순위를 가진 다른 스레드에게 실행을 양보하고 즉시 runnable상태가 변경된다.
1 | static class YieldThread extends Thread{ |
스레드의 종료
- run()스레드가 끝나면 자동으로 종료
- 외부조건에 의해 스레드 종료 가능 : stop()메서드가 있지만 안정성을 위해 flag값을 이용해서 내부값을 자연스럽게 종료시켜야한다.
- 종료된 메서드는 생존주기가 소멸된다 -> 한 번 소멸된 스레드는 다시 start()를 통해서 runnable로 이동할 수 없다.
- 예시 : stop()과 flag사용법 차이
1 | static class Rut extends Thread{ |
- flag는 자원반납까지가능
- stop은 자원반납못하고 즉시 종료됨