프로세스와 쓰레드

InOut·2023년 5월 12일
post-thumbnail

프로세스와 쓰레드

프로세스 : 실행중인 프로그램, 자원(resources)과 쓰레드로 구성

쓰레드 : 프로세스 내에서 실제 작업을 수행하는 단위
모든 프로세스는 최소한 하나의 쓰레드를 가지고 있다

싱글쓰레드 프로세스 -> 프로세스 하나당 쓰레드 1개
멀티쓰레드 프로세스 -> 프로세스 하나당 쓰레드 n개

멀티쓰레드의 장단점

// 장점

  • 시스템 자원을 보다 효율적으로 사용 가능
  • 사용자에 대한 응답성이 향상됨
  • 작업이 분리되어 코드가 간결

// 단점

  • 동기화에 주의해야 함
  • 교착상태(Dead-Lock)가 발생하지 않도록 주의해야 함
  • 각 쓰레드가 효율적으로 고르게 실행 될 수 있게 해야함
    -- 프로그래밍 할 때 고려해야 할 사항들이 많다

쓰레드의 구현

  1. Thread클래스를 상속
class MyThread extends Thread {
	public void run()	{ // Thread클래스의 run()을 오버라이딩
    	/* 작업내용 */
        }
 }
 
 
 MyThread t1 = new MyThread();	// 쓰레드 생성
 t1.start();					// 쓰레드 실행
  1. Runnable인터페이스를 구현
class MyThread2 implements Runnable {
	public void run() { // Runnable인터페이스의 추상메서드 run()을 구현
    	/* 작업내용 */
    }
}


Runnable r = new MyThread2()
Thread	t2 = new Thread(r);	  // Thread(Runnable r)
// Thread t2 = new Thread(new MyThread2());	// 위의 두줄과 같음
t2.start();			

쓰레드의 실행

  • 쓰레드를 생성한 후에 start()를 호출해야 쓰레드가 작업을 시작한다.
MyThread t1 = new MyThread();	// 쓰레드 t1 생성
MyThread t2 = new MyThread();	// 쓰레드 t2 생성

t1.start();	// 쓰레드 t1을 실행
t2.start();	// 쓰레드 t2를 실행
  • 스타트 했다고 즉시 쓰레드가 실행되는건 아님
  • 먼저 스타트했다고 먼저 실행되는것은 아님
  • 쓰레드를 스타트하면 실행 가능한 상태가 되는 것
  • 언제 실행될지는 OS 스케쥴러가 실행순서를 결정

main쓰레드

  • main메서드의 코드를 수행하는 쓰레드
  • 쓰레드는 '사용자 쓰레드'와 '데몬 쓰레드' 두 종류가 있다.
  • 실행 중인 사용자 쓰레드가 하나도 없을 때 프로그램은 종료된다.

쓰레드의 우선순위

  • 작업의 중요도에 따라 쓰레드의 우선순위를 다르게 하여 특정 쓰레드가 더 많은 작업시간을 갖게 할 수 있다.
void setPriority(int newPriority) // 쓰레드의 우선순위를 지정한 값으로 변경한다
int getPriority()				  // 쓰레드의 우선순위를 반환한다

public static final int MAX_PRIOITY	  = 10	// 최대우선순위
public static final int MIN_PRIOITY	  = 1	// 최소우선순위
public static final int NORM_PRIORITY = 5	// 보통우선순위
  • 우리가 정해주는 쓰레드의 우선순위는 희망사항을 담아서 OS스케쥴러에게 전달 해줄 뿐이다
    • OS에서 돌아가고 있는 수많은 프로세스와 쓰레드들을 무시하고 우리가 실행하는 프로그램에만 특혜를 줄 수 없다
    • OS에서 실행중인 프로세스와 쓰레드가 골고루 잘 수행되도록 하는것이 OS스케쥴러의 역할이기 때문이다

쓰레드 그룹

  • 서로 관련된 쓰레드를 그룹으로 묶어서 다루기 위한 것
  • 모든 쓰레드는 반드시 하나의 쓰레드 그룹에 포함되어 있어야 함
  • 쓰레드 그룹을 지정하지 않고 생성한 쓰레드는 'main쓰레드 그룹'에 속함
  • 자신을 생성한 쓰레드(부모 쓰레드)의 그룹과 우선순위를 상속받음
Thread(ThreadGroup group, String name)
Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)

ThreadGroup getThreadGroup() //쓰레드 자신이 속한 쓰레드 그룹을 반환
void uncaughtException(Thread t, Throwable e) // 처리되지 않은 예외에 의해 
쓰레드 그룹의 쓰레드가 실행이 종료되었을 때 , JVM에 의해 이 메서드가 자동적으로 호출

데몬 쓰레드(daemon thread)

  • 일뱐 쓰레드의 작업을 돕는 보조적인 역할을 수행
  • 일반 쓰레드가 모두 종료되면 자동적으로 종료됨
  • 가비지 컬렉터, 자동저장, 화면 자동갱신 등에 사용된다.
  • 무한루프와 조건문을 이용해서 실행 후 대기하다가 특정조건이 만족되면 작업을 수행하고 다시 대기하도록 작성한다.
public void run() {
	while(true) {
    	try {
        	Thread.sleep(3 * 1000);
        } catch(InterruptedException e) {}
        
        // autoSave의 값이 true이면 autoSave()를 호출한다.
        if(autoSave) {
        	autoSave();
        }
    }
}
boolean isDaemon() // 쓰레드가 데몬쓰레드인지 확인. 데몬쓰레드면 true 반환
void setDaemon(boolean on) // 쓰레드를 데몬쓰레드 또는 사용자 쓰레드로 변경.
						매개변수 on을 true로 지정하면 데몬 쓰레드가 된다.
  • setDaemon(boolean on)은 반드시 start()를 호출하기 전에 실행되어야 한다.
    그렇지 않으면 illegalThreadStateException이 발생한다.

쓰레드의 상태

  1. NEW

    • 쓰레드가 생성되고 아직 start()가 호출되지 않은 상태
  2. RUNNABLE

    • 실행 중 또는 실행 가능한 상태
  3. BLOCKED

    • 동기화블럭에 의해서 일시정지된 상태(lock이 풀릴 때 까지 기다리는 상태)
  4. WAITING, TIMED_WAITING

    • 쓰레드의 작업이 종료되지는 않았지만 실행가능하지 않은 일시정지상태. TIMED_WAITING은 일시정지시간이 지정된 경우를 의미
  5. TERMINATED
    - 쓰레드의 작업이 종료된 상태

    쓰레드의 실행제어

  • 쓰레드의 실행을 제어할 수 있는 메서드가 제공된다.
  • 아래 메서드를 활용해서 보다 효율적인 프로그램의 작성할 수 있다.
// 지정된 시간(천분의 일초 단위)동안 쓰레드를 일시정지 시킨다. 
지정한 시간이 지나고나면, 자동적으로 다시 실행대기 상태가 된다.
static void sleep(long millis)
static void sleep(long millis, int annos)

// 지정된 시간동안 쓰레드가 실행되도록 한다. 
지정된 시간이 지나거나 작업이 종료되면 join()을 호출한 쓰레드로 다시 돌아와 실행을 계속한다
void join()				// 다른 쓰레드 기다리기
void join(long millis)
void join(long millis, int nanos)

// sleep()이나 join()에 이ㅡ해 일시정지상태인 쓰레드를 깨워서 실행대기상태로 만든다.
해당 쓰레드에서는 interruptedException이 발생함으로써 일시정지상태를 벗어나게 된다.
void interrupt()

// 쓰레드를 즉시 종료시킨다.
void stop()

// 쓰레드를 일시정지시킨다. resume()을 호출하면 다시 실행대기상태가 된다.
void suspend()

// suspend()에 의해 일시정지상태에 있는 쓰레드를 실행대기상태로 만든다.
void resume()

// 실행 중에 자신에게 주어진 실행시간을 다른 쓰레드에게 양보하고 자신은 실행대기상태가 된다.
static void yield()

sleep()

  • 현재 쓰레드를 지정된 시간동안 멈추게한다.
  • static 메서드 = 쓰레드 자기 자신에 대해서 동작(다른 쓰레드를 지정해서 멈추게 하는 것은 불가능
static void sleep(long millis)				// 천분의 일초 단위
static void sleep(long millis, int annos)	// 천분의 일초 + 나노초
  • 예외처리를 해야한다.(InterruptedException이 발생하면 깨어남)
try {
	Thread.sleep(1, 500000); // 쓰레드를 0.0015초 동안 멈추게 함
} catch(InterruptedException e) {}	// 아무것도 안넣어도됨. 어떤 문제가 있어서 
										발생한게 아니라 예외와 try_catch를 
                                        이용해서 잠자는 상태를 벗어나게 만든거라
                                        catch문에서 할 게 없다.
  • 항상 예외처리하기 힘드니 다음과 같이 따로 메서드로 만들어놓고 사용하면 좋다
void delay(long millis) {
	try {
    	Thread.sleep(millis);
    } catch (InterruptedException e) {}
}

-----------------------------------------
delay(15);

interrupt()

  • 대기상태(WAITING)인 쓰레드를 실행대기(RUNNABLE)상태로 만든다
void 	interrupt()			쓰레드의 interrupted상태를 false에서 true로 변경
boolean isInterrupted()		쓰레드의 interrupted상태를 반환
static boolean interrupted()    현재 쓰레드의 interrupted상태를 알려주고, 
								false로 초기화

suspend(), resume(), stop()

  • deprecated된 메서드들이라 사용을 권장하지 않는다.(dead-lock 발생가능성으로 인해)
void suspend()	쓰레드를 일시정지 시킨다.
void resume()	suspend()에 의해 일시정지된 쓰레드를 실행대기상태로 만든다.
void stop()		쓰레드를 즉시 종료시킨다.
// 직접 구현할 경우 간단한 예제
class ThreadEx1 implements Runnable {
	boolean suspended = false;
    boolean stopped   = false;
    
    public void run() {
    	while(!stopped) {
        	if(!suspended) {
            	/* 쓰레드가 수행할 코드를 작성 */
            }
        }
    }
    
    public void suspend () { suspended = true;  }
    public void resume ()  { suspended = false; }
    public void stop ()	   { stopped   = true;	}              

join()

  • 지정된 시간동안 특정 쓰레드가 작업하는 것을 기다린다.
void join()							// 작업이 모두 끝날 때 까지
void join(long nmillis)				// 천분의 일초동안
void join(long millis, int nanos)	// 천분의 일초 + 나노초 동안
  • 예외처리를 해야한다.(InterruptedException이 발생하면 재개)

yield()

  • 남은 시간을 다음 쓰레드에게 양보하고, 자신(현재 쓰레드)은 실행대기한다.
  • static 메서드(자기 자신에게만 사용 가능)
  • yield()와 interrupt()를 적절히 사용하면, 응답성과 효율을 높일 수 있다.
  • yield()는 os스케쥴러에게 통보하는것이기 때문에 보장할 수는 없다.

쓰레드의 동기화(synchronization)

  • 멀티 쓰레드 프로세스에서는 다른 쓰레드의 작업에 영향을 미칠 수 있다.
  • 진행중인 작업이 다른 쓰레드에게 간섭받지 않게 하려면 '동기화'가 필요하다

쓰레드의 동기화 - 한 쓰레드가 진행중인 작업을 다른 쓰레드가 간섭하지 못하게 막는것

  • 동기화하려면 간섭받지 않아야 하는 문장들을 '임계 영역'으로 설정
  • 임계영역은 락(lock)을 얻은 단 하나의 쓰레드만 출입가능(객체 1개에 락 1걔)

synchronized를 이용한 동기화

synchronized로 임계영역(lock을 걸리는 영역)을 설정하는 방법

  1. 메서드 전체를 임계 영역으로 지정
public synchronized void calcSum() {
	// ...
}
  1. 특정한 영역을 임계 영역으로 지정(메서드 보단 이 방법을 추천)
synchronized(객체의 참조변수) {
	// ...
}
profile
개발새발

0개의 댓글