프로세스(process) 1) '실행 중인 프로그램(program)'이다. 프로그램을 실행하면 OS로부터 실행에 필요한 자원(메모리)을 할당받아 프로세스가 된다. 2) 프로그램을 수행하는 데 필요한 데이터와 메모리 등의 자원 그리고 스레드로 구성되어 있다. 3) 작업 공간(공장)
스레드 1) 프로세스의 자원을 이용해서 실제로 작업을 수행하는 것이다. 2) 모든 프로세스에는 최소한 하나 이상의 스레드가 존재하며, 둘 이상의 스레드를 가진 프로세스를 '멀티스레드 프로세스(multi-threaded process)'라고 한다. 3) 싱글스레드 = 자원 + 스레드, 멀티쓰레드 = 자원 + 쓰레드 + 쓰레드 +... 4) 일꾼
멀티쓰레딩 1) 하나의 프로세스 내에서 여러 스레드가 동시에 작업을 수행하는 것이다. CPU의 코어는 한 번에 단 하나의 작업만 수행할 수 있으므로 실제로 동시에 처리되는 작업의 개수는 코어의 개수와 일치한다. 그러나 처리해야 하는 스레드의 수는 대부분 코어의 개수보다 훨씬 많기 때문에 각 코어가 짧은 시간 동안 여러 작업을 번갈아 가며 수행함으로써 여러 작업들이 모두 동시에 수행되는 것처럼 보이게 한다. 2) 단점 동기화(synchronization), 교착상태(deadlock, 두 스레드가 자원을 점유한 상태에서 서로 상대편이 점유한 자원을 사용하려고 기다리느라 진행이 멈춰있는 상태) ∵ 여러 쓰레드가 같은 프로세스 내에서 자원을 공유하면서 작업을 하기 때문에 발생
2. 스레드의 구현과 실행
스레드를 구현하는 방법 두 가지
Thread 클래스 상속 : extends
Runnable 인터페이스 구현 : implements
스레드의 실행 스레드는 생성 후 start()를 호출해야만 실행된다. 호출되었다고 해서 바로 실행되는 것은 아니고, 일단 실행대기 상태에 있다가 자신의 차례가 되면 실행된다. 또한 한 번 실행이 종료된 쓰레드는 다시 실행할 수 없다.
start() 새로운 스레드가 작업을 실행하는데 필요한 호출스택(call stack)을 생성한 다음에 run()을 호출해서, 생성된 호출스택에 run()이 첫 번째로 올라가게 하는 것 모든 스레드는 독립적인 작업을 수행하기 위해 자신만의 호출스택을 필요로 하기 때문에, 새로운 스레드를 생성하고 실행시킬 때마다 새로운 호출스택이 생성되고 쓰레드가 종료되면 작업에 사용된 호출스택은 소멸된다.
run() 생성된 쓰레드를 실행시키는 것이 아니라 단순히 클래스에 선언된 메서드를 호출하는 것
main 쓰레드
main 메서드의 작업을 하는 스레드를 main 쓰레드라고 한다. 프로그램을 실행하면 기본적으로 하나의 쓰레드를 생성하고, 그 스레드가 main 메서드를 호출해서 작업이 수행되도록 한다. main 메서드가 수행을 마쳤다하더라고 다른 쓰레드가 아직 작업을 마치지 않은 상태라면 프로그램이 종료되지 않는다.
실행 중인 사용자 쓰레드가 하나도 없을 때 프로그램은 종료된다.
스레드의 두 종류: 사용자 쓰레드 = 비데몬 쓰레드, 데몬 쓰레드
데몬 스레드(daemon thread)
다른 일반 스레드(데몬 스레드가 아닌 스레드)의 작업을 돕는 보조적인 역할을 수행하는 스레드이다.
데몬 스레드는 일반 스레드의 보조역할을 수행하므로 일반 스레드가 모두 종료되고 나면 데몬 쓰레드는 존재의 의미가 없다. 따라서 일반 쓰레드가 모두 종료되면 데몬 쓰레드는 강제적으로 자동 종료된다.
3. 쓰레드의 실행제어
상태
설명
NEW
쓰레드가 생성되고 아직 start()가 호출되지 않은 상태
RUNNABLE
실행 중 또는 실행 가능한 상태
BLOCKED
동기화블럭에 의해서 일시정지된 상태(lock이 풀릴 때까지 기다리는 상태)
WAITING, TIMED_WAITING
쓰레드의 작업이 종료되지는 않았지만 실행가능하지 않은(unrunnable) 일시정지 상태 TIMED_WAITING은 일시정지시간이 지정된 경우를 의미
TERMINATED
쓰레드의 작업이 종료된 상태
※ 아래 쓰인 순서대로 쓰레드가 실행되는 것은 아니다.
스레드를 생성하고 start()를 호출하면 바로 실행되는 것이 아니라 실행대기열에 저장되어 자신이 차례가 될 때까지 기다린다. 실행대기열은 큐(queue)와 같은 구조로 먼저 실행대기열에 들어온 스레드가 먼저 실행된다.
실행대기상태에 있다가 자신의 차례가 되면 실행상태가 된다.
주어진 실행시간이 다되거나 yield()를 만나면 다시 실행대기상태가 되고 다음 차례의 쓰레드가 실행상태가 된다.
실행 중에 suspend(), sleep(), wait(), join(), I/O block에 의해 일시정지상태가 될 수 있다. I/O block은 입출력작업에서 발생하는 지연상태를 말한다. 사용자의 입력을 기다리는 경우를 예로 들을 수 있는데, 이런 경우 일시정지 상태에 있다가 사용자가 입력을 마치면 다시 실행대기상태가 된다.
지정된 일시정지시간이 다되거나(time-out), notify(), resume(), interrupt()가 호출되면 일시정지상태를 벗어나 다시 실행대기열에 저장되어 자신의 차례를 기다리게 된다.
실행을 모두 마치거나 stop()이 호출되면 스레드는 소멸한다.
메서드
설명
sleep(long millis)
일정(지정된) 시간동안 쓰레드를 멈추게 한다. sleep()에 의해 일시정지 상태가 된 쓰레드는 지정된 시간이 다 되거나 interrupt()가 호출되면(InterruptedException 발생) 잠에서 깨어나 실행대기 상태가 된다.
interrupt(), interrupted()
쓰레드의 작업을 취소한다. 진행 중인 쓰레드의 작업이 끝나기 전에 취소시켜야할 때, 그저 쓰레드에게 작업을 멈추라고 요청하는 것일 뿐, 쓰레드를 강제로 종료시키지는 못한다. 쓰레드가 sleep(), wait(), join()에 의해 일시정지 상태(WAITING)에 있을 때 해당 쓰레드에 대해 interrupt()를 호출하면, sleep(), wait(), join() 상태에서 Interrupted Exception이 발생하고 쓰레드는 실행대기 상태(RUNNABLE))로 바뀐다.
suspend(), resume(), stop()
쓰레드의 실행을 제어하는 가장 손쉬운 방법이지만, suspend()와 stop()이 교착상태(deadlock)을 일으키기 쉽게 작성되어 있어 사용이 권장되지는 않는다. → deprecated 되었다.
suspend()는 sleep()처럼 쓰레드를 멈추게 한다. suspend()에 의해 정지된 쓰레드는 resume()을 호출해야 다시 실행대기 상태가 된다. stop()은 호출되는 즉시 쓰레드가 종료된다.
yield()
쓰레드 자신에게 주어진 실행시간을 다음 차례의 쓰레드에게 양보(yield)한다. 예를 들어 스케쥴러에 의해 1초의 실행시간을 할당받은 쓰레드가 0.5초의 시간동안 작업한 상태에서 yield()가 호출되면, 나머지 0.5초는 포기하고 다시 실행대기상태가 된다.
join()
쓰레드 자신이 하던 작업을 잠시 멈추고 다른 쓰레드가 지정된 시간동안 작업을 수행하도록 한다. 시간을 지정하지 않으면, 해당 쓰레드가 작업을 모두 마칠 때까지 기다리게 된다. 작업 중에 다른 쓰레드의 작업이 먼저 수행되어야할 필요가 있을 때 join()을 사용한다. sleep()처럼 interrupt()에 의해 대기상태에서 벗어날 수 있다. sleep()과 다른 점은 jpin()은 현재 쓰레드가 아닌 특정 쓰레드에 대해 동작하므로 static 메서드가 아니다.
4. 쓰레드 동기화(synchronization)
싱글스레드 프로세스의 경우에는 별문제 없지만, 멀티스레드 프로세스의 경우 여러 스레드가 같은 프로세스 내의 자원을 공유해서 작업하기 때문에 서로의 작업에 영향을 주게 된다. 예를 들어, 쓰레드A가 작업하던 도중에 다른 쓰레드B에게 제어권이 넘어갔을 때, 쓰레드A가 작업하던 공유데이터를 쓰레드B가 임의로 변경하였다면, 다시 쓰레드A가 제어권을 받아서 나머지 작업을 마쳤을 때 원래 의도하던 것과는 다른 결과를 얻을 수 있다.
∴ 임계 영역(critical section)과 잠금(lock)이라는 개념이 도입되었다. - 한 쓰레드가 특정 작업을 끝마치기 전까지 다른 스레드에 의해 방해받지 않도록 하는 것
공유 데이터를 사용하는 코드 영역을 임계 영역으로 지정해 놓고, 공유 데이터(객체)가 가지고 있는 lock을 획득한 단 하나의 스레드만 이 영역 내의 코드를 수행할 수 있게 한다. 그리고 해당 스레드가 임계 영역 내의 모든 코드를 수행하고 벗어나서 lock을 반납해야만 다른 쓰레드가 반납된 lock을 획득하여 임계 영역의 코드를 수행할 수 있게 된다.
∴ 한 쓰레드가 진행 중인 작업을 다른 쓰레드가 간섭하지 못하도록 막는 것을 스레드의 동기화(synchronization)라고 한다.
Java에서 모든 객체는 monitor를 가지고 있으며 이 monitor 내부에 lock이 존재하고, 해당 객체의 lock을 가지고 있는 스레드만 임계 영역의 코드를 수행할 수 있다. 다른 스레드들은 lock을 얻을 때까지 기다리게 된다.
wait()과 notify()
동기화된 임계 영역의 코드를 수행하다가 작업을 더 이상 진행할 상황이 아니면, 일단 wait()을 호출하여 스레드가 락을 반납하고 기다리게 한다. 그러면 다른 쓰레드가 락을 얻어 해당 객체에 대한 작업을 수행할 수 있게 된다. 나중에 작업을 진행할 수 있는 상황이 되면 notify()를 호출해서, 작업을 중단했던 쓰레드가 다시 락을 얻어 작업을 진행할 수 있게 한다.
wait()으로 lock을 풀고 waiting pool에서 기다리다가 음식이 추가되면 notify()로 통보를 받고 다시 lock을 얻어서 나머지 작업을 진행할 수 있게 한다.
notifyAll()
생산자 스레드는 계속 통지를 받지 못하고 오랫동안 기다리게 되는데, 이것을 기아(starvation) 현상이라고 한다. 이 현상을 막으려면 notifyAll()을 사용해야 한다. 일단 모든 스레드에게 통지를 하면 소비자 스레드는 다시 waiting pool에 들어가더라고 생산자 쓰레드는 결국 lock을 얻어 작업을 진행할 수 있기 때문이다.
notifyAll()로 기아 현상은 막았지만, 소비자 스레드까지 통지를 받아서 불필요하게 요리사 쓰레드와 lock을 얻기 위해 경쟁하게 된다. 이처럼 여러 쓰레드가 lock을 얻기 위해 서로 경쟁하는 것을 경쟁/경합 상태(race condition)라고 한다.
스레드 덤프 획득 방법 1) kill -3 [PID] → log 파일 확인 날짜 Full thread dump Java ~. 위로 다 날리기 2) jstack [PID] 3) ctrl + break windows 환경(안 해봄)
※ 생성 시 3~5회 연속으로 생성하여 문제 상황에 대한 변화 과정을 확인
6. WebLogic에서의 스레드 모니터링
Self-Tuning(WebLogic Server 9 이상부터 적용) 1) 9 미만 여러 개의 실행 큐(execute queue)를 통해 처리를 수행(우선순위 및 순서 요구사항, 교착 상태를 피하기 위해 서로 다른 큐에서 실행), 기본 실행 큐인 weblogic.kernel.default 외에도, weblogic.admin.HTTP 및 weblogic.admin.RMI와 같은 내부 관리 트래픽을 위한 사전 구성된 큐들이 존재
2) 9 이상 모든 종류의 작업을 하나의 스레드 풀에서 실행, 모든 종류의 작업(HTTP 요청, RMI, 내부 작업 등)이 단일 풀에서 처리, 공통 스레드 풀은 처리량을 최대화하기 위해 자동으로 크기를 조정 - 과거 데이터에서 스레드 수를 늘릴수록 처리량이 증가했으면 → 스레드 수 증가 - 스레드 수를 줄여도 처리량에 영향이 없었다면 → 스레드 수 감소
3) 테스트(default 값 사용, 최소 1개, 최대 400개) - 테스트 전 초기 상태(총 16개)
4) 테스트2 - config.xml 설정: 구성 > 튜닝: 고급 - 자체 튜닝 스레드 최소 풀 크기, 자체 튜닝 스레드 최대 풀 크기 - 기동 스크립트 설정: -Dweblogic.SelfTuningThreadPoolSizeMin=<value> -Dweblogic.SelfTuningThreadPoolSizeMax=<value> - 15개로 설정하고 테스트 시 15개 이상 스레드가 생성되지 않음. Admin Console의 모니터링 > 스레드에서 보류 중인 요청 수가 19인 걸 보면, 현재 WebLogic이 처리할 수 있는 스레드는 모두 사용 중이고, 추가 요청 19개가 실행을 기다리며 큐에 쌓여 있는 상황이라는 걸 알 수 있다.
웹로직 서버는 어떤 스레드가 일정 시간동안 계속해서 일하고 있으면 (idle 상태가 아니라) STUCK 상태에 있다고 판단하게 된다.
Admin Console 설정
구성 > 튜닝 - Stuck Thread Max Time(막힌 스레드 최대 시간, default 600초): 어떤 스레드가 지정한 시간(예: 600초) 이상 지속적으로 작업을 수행하며 응답하지 않을 경우, WebLogic은 그 스레드를 stuck으로 판단 - Stuck Thread Timer Interval(막힌 스레드 타이머 시간, default 60초): WebLogic 서버가 주기적으로(간격을 지정) stuck thread 상태를 점검하는 타이머 간격
// WebLogic 기본 Stuck Thread 기준은 600초 (10분) int sleepSeconds = 700; // 700초 = 11분 40초
try { System.out.println("▶ Stuck 테스트 시작: " + new Date()); Thread.sleep(sleepSeconds * 1000L); } catch (InterruptedException e) { e.printStackTrace(); }
long end = System.currentTimeMillis(); %> <html> <head><title>Stuck Thread 테스트</title></head> <body> <h2>Stuck 테스트 완료</h2> <p>처리 시간: <%= (end - start) / 1000 %> 초</p> <p>시작 시각: <%= new Date(start) %></p> <p>종료 시각: <%= new Date(end) %></p> </body> </html>
[Managed Server Log] ▶ Stuck 테스트 시작: Mon Jun 16 15:36:59 KST 2025 <Jun 16, 2025 3:37:34,584 PM KST> <Info> <WorkManager> <BEA-002959> <Self-tuning thread pool contains 1 running threads, 2 idle threads, and 13 standby threads> <Jun 16, 2025 3:43:18,257 PM KST> <Info> <Health> <BEA-310002> <54% of the total memory in the server is free.> <Jun 16, 2025 3:47:18,261 PM KST> <Info> <Health> <BEA-310002> <78% of the total memory in the server is free.> <Jun 16, 2025 3:47:18,413 PM KST> <Error> <WebLogicServer> <BEA-000337> <[STUCK] ExecuteThread: '0' for queue: 'weblogic.kernel.Default (self-tuning)' has been busy for "619" seconds working on the request "Http Request Information: weblogic.servlet.internal.ServletRequestImpl@366b42ca[GET /thread/stuckThread.jsp] ", which is more than the configured time (StuckThreadMaxTime) of "600" seconds in "server-failure-trigger". Stack trace: java.lang.Thread.sleep(Native Method) jsp_servlet.__stuckthread._jspService(__stuckthread.java:103) weblogic.servlet.jsp.JspBase.service(JspBase.java:35) ... weblogic.work.ExecuteThread.run(ExecuteThread.java:360) > <Jun 16, 2025 3:47:35,045 PM KST> <Info> <WorkManager> <BEA-002959> <Self-tuning thread pool contains 1 running threads, 2 idle threads, and 13 standby threads> <Jun 16, 2025 3:48:18,414 PM KST> <Error> <WebLogicServer> <BEA-000337> <[STUCK] ExecuteThread: '0' for queue: 'weblogic.kernel.Default (self-tuning)' has been busy for "679" seconds working on the request "Http Request Information: weblogic.servlet.internal.ServletRequestImpl@366b42ca[GET /thread/stuckThread.jsp] ", which is more than the configured time (StuckThreadMaxTime) of "600" seconds in "server-failure-trigger". Stack trace: java.lang.Thread.sleep(Native Method) jsp_servlet.__stuckthread._jspService(__stuckthread.java:103) weblogic.servlet.jsp.JspBase.service(JspBase.java:35) ... weblogic.work.ExecuteThread.run(ExecuteThread.java:360) > <Jun 16, 2025 3:48:39,440 PM KST> <Info> <WebLogicServer> <BEA-000339> <[ACTIVE] ExecuteThread: '0' for queue: 'weblogic.kernel.Default (self-tuning)' has become "unstuck".> <Jun 16, 2025 3:49:35,119 PM KST> <Info> <WorkManager> <BEA-002959> <Self-tuning thread pool contains 0 running threads, 3 idle threads, and 13 standby threads>