본문 바로가기
JAVA/객체지향

프로세스와 쓰레드

by chogigang 2023. 2. 24.

프로세스

 

실행중인 프로그램  프로그램을 실행하면OS로 부터 실행에 필요한 자원(메모리)을 할당받아 프로세스가 됩니다.

 

모든 프로세스에는 최소 하나 이상의 쓰레드가 존재합니다

둘이상의 쓰레드를 가진 프로세스를 '멀티쓰레드 프로세스' 라고 합니다.

 

프로세스가 가질 수 있는 쓰레드의 개수는 제한되 었지 않습니다

메모리 공간(호출스택)을 필요로 하기 때문에 메모리 한계에 따라 생성할 수 있는쓰레드의 수가 결정됩니다.

 

 

 

 

멀티쓰레딩의 장점

  • cpu의 사용률을 향상시킵니다
  • 자원을 보다 효율적으로 사용할 수 있습니다
  • 사용자에 대한 응답성이 향상됩니다
  • 작업이 분리되어 코드가 간결해집니다.

 

멀티쓰레딩의 단점

  • 프로세스 내에서 자원을 공유하면서 작업하기 때문에 발생할 수 있는 동기화,교착상태,기아(굶어죽는거)같은 문제들을 고려해야합니다
  • 각 쓰레드가 효율적으로 고르게 실행될 수 있도록 해야 합니다.
    프로그래밍할 때 고려해야 할 사항들이 많습니다.

 

쓰레드의 구현과 실행

    Thread클래스를 상속
class MyThread extends Thread{
        public void run(){작업내용} //Thread클래스의 run()을 오버라이딩
}

Runnable 인터페이스를 구현
    class MyThread implements Runnable{
        public void run(){작업내용} //Runnable인터페이스의 run()을 구현
    }

 

 

쓰레드의 실행 - start()

 

 

Start()와run()

 

 

 

 

Main쓰레드

main메서드의 작업을 수행하는 것도 쓰레드이며 이를 main쓰레드라고 합니다

 

실행 중인 사용자 쓰레드가 하나도 없을 때 프로그램은 종료됩니다.

 

쓰레드는 사용자 쓰레드와 데몬 쓰레드 두종류가 있습니다.

 

싱글쓰레드와 멀티 쓰레드

[하나의 쓰레드 VS 두개의 쓰레드]

싱글쓰레드가 작업속도가 더 빠른 이유는 작업 전환 다음에 실행해야할 위치 등의 정보를 저장하고

읽어 오는 시간이 소요되므로

 싱글코어에서 단순히 CPU만을 사용하는 계산작업은 싱글쓰레드가 더 효율적입니다.

 

 

멀티코어일 경우 작업이 겹치는 부분이 생기는데, 이 경우는 한 쓰레드가 화면에 출력하고 있는 동안 다른 쓰레드는 대기상태입니다.

두 쓰레드가 서로 다른 자원을 사용할 경우에는 멀티쓰레드가 효율적입니다

다른 쓰레드가 입력을 기다릴때 또 다른 쓰레드가 일을 처리하면 되기 때문에 더효율적입니다.

 

쓰레드의 I/O 블락킹

두 쓰레드가 서로 다른 자원을 사용하는 작업의 경우에는 싱글쓰레드 프로세스보다 멀티쓰레드 프로세스가 더 효율적이다. 예를 들면 사용자로부터 데이터를 입력받는 작업, 네트워크로 파일을 주고받는 작업, 프린터로 파일을 출력하는 작업과 같이 외부기기와 입출력을 필요로 하는 경우가 이에 해당합니다.

 

만일 사용자로부터 입력받는 작업(A)과 화면에 출력하는 작업(B)을 하나의 쓰레드로 처리한다고 하면 사용자가 입력을 마칠 때까지 아무 일도 하지 못하고 기다리기만 해야한다. 그러나 두개의 쓰레드로 처리한다면 사용자의 입력을 기다리는 동안 다른 쓰레드가 작업을 처리할 수 있기 때문에 보다 효율적인 CPU의 사용이 가능합니다.

 

쓰레드가 입출력(I/O)처리를 위해 기다리는 것을 I/O블락킹이라고 합니다.

 

public class ThreadTest {
    public static void main(String[] args) {
        ThreadC th1 = new ThreadC();
        th1.start();

        // 1. 사용자로부터 입력받기
        String input = JOptionPane.showInputDialog("아무 값이나 입력하세요");
        System.out.println("입력하신 값은 " + input + "입니다.");
    }
}

class ThreadC extends Thread{
    @Override
    public void run() { // 2. 카운트 다운
        for (int i = 10; i > 0; i--) {
            System.out.println(i);
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }
}

 

 

쓰레드의 우선순위

쓰레드 우선순위라는 속성을 가지고 있습니다 이 우선순위의 값에 따라 쓰레드가 얻는 실행시간이 달라집니다.

 

쓰레드가 수행하는 작업의 중요도에 따라서 쓰레드의 우선순위를 서로 다르게 지정하여 특정 쓰레드가 더 많은 작업시간을 갖도록 할 수 있습니다

 

쓰레드의 우선순위 지정하기

 

1~10까지 있습니다.  

void setPriority(int newPriority)//쓰레드의 우선순위를 지정한 값으로 변경한다.
    int getPriority() //쓰레드의 우선순위를 반환한다.
    public static final int MAX PRIORITY = 10 //최대우선순위
    public static final int MIN PRIORITY = 1 //최소우선순위
    public static final int NORM PRIORITY = 5 // 보통우선순위

 

쓰레드 그룹

 

서로 관련된 쓰레드를 그룹 입니다

 

모든 쓰레드는 반드시하나의 쓰레드 그룹에 포함되어 있어야합니다

쓰레드 그룹을 지정하지 않고 생성한 쓰레드는 main쓰레드 그룹에 속합니다

자신을 생성한 쓰레드(부모 쓰레드)의 그룹과 우선순위를 상속 받습니다

 

 

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

 

데몬 쓰레드

 

다른 일반 쓰레드의 작업을 돕는 보조적인 역할을 햇씁니다 일반쓰레드가 모두 종료되면 데몬 쓰레드는 강제적으로 자동 종료됩니다.   

 

가비지 컬렉터,워드프로세서의 자동저장,화면자동갱신 등이 있습니다.

(쓰지않는 메모리 제거)

public void run() {
        while (true){//무한루프
            try{
                Thread.sleep(3* 1000); //3초마다 쉽니다.
            }catch (InterruptedException e) {
                
            }
        }
}
setDemon메서드에서는 반드시 start()를 호출하기 전에 실행되어야합니다.

 

쓰레드의 실행제어

 

 

메서드 설 명
static void sleep(long millis)
static void sleep(long millis, int nanos)
지정된 시간(천분의 1초 단위)동안 쓰레드를 일시정지시킨다. 지정한 시간이 지나고 나면, 자동적으로 다시 실행대기상태가 된다. 
void join()
void join(long millis)
void join(long millis, int nanos)
지정된 시간동안 쓰레드가 실행되도록 한다. 지정된 시간이 지나거나 작업이 종료되면 join()을 호출한 쓰레드로 다시 돌아와 실행을 계속한다.
void interrupt() sleep()이나 join()에 의해 일시정지상태인 쓰레드를 깨워서 실행대기상태로 만든다.
해당 쓰레드에서는 InterruptedException이 발생함으로써 일시정지상태를 벗어나게 한다.
void stop() [안씀] 쓰레드를 즉시 종료시킨다.
void suspend() [안씀] 쓰레드를 일시정지시킨다. resume()을 호출하면 다시 실행대기상태가 된다.
void resume() [안씀] suspend()에 의해 일시정지상태에 있는 쓰레드를 실행대기상태로 만든다.
static void yield() 실행 중에 자신에게 주어진 실행시간을 다른 쓰레드에게 양보(yield)하고 자신은 실행대기상태가 된다.

 

상태 설명
NEW 쓰레드가 생성되고 아직 start()가 호출되지 않은 상태
RUNNABLE 실행 중 또는 실행 가능한 상태
BLOCKED 동기화블럭에 의해서 일시정지된 상태(lock이 풀릴 때까지 기다리는 상태)
WAITING,
TIMED_WAITING
쓰레드의 작업이 종료되지는 않았지만 실행가능하지 않은(unrunnable) 일시정지 상태.
TIMED_WAITING은 일시정지시간이 지정된 경우를 의미.
TERMINATED 쓰레드의 작업이 종료된 상태

 

 

 

sleep -일정시간동안 쓰레드를 멈추게 합니다.

 

static void sleep(long millis) //잠잘시간 3초=3*1000
    static void sleep(long millis,int nanos)
        

sleep()에 의해 일시정지 상태가 된 쓰레드는 지정된 시간이 다 되거나 interrupt()가 호출되면(InterruptedException발생),

잠에서 깨어나 실행대기 상태가 됩니다 그래서 sleep()을 호출할 때는 항상 try-catch문으로 예외를 처리해줘야 합니다

 

void delay(long millis){
        try{
            Thread.sleep(millis);
        } catch (InterruptedException e) {}//Exception의 자손 필수로 예외 처리
  
    }

interrupt

쓰레드의 작업을 취소

 

대기상태(작업중단)인 쓰레드를 실행대기 상태로 만듭니다.

void interrupt()//쓰레드의 isInterrupted상태를 false에서 true로 변경
    boolean isInterrupted() //쓰레드의 isInterrupted상태를 반환.
        short boolean interrupted()//현재 쓰레드의 isInterrupted상태를 반환 후, false로 변경

 

yield()

static메서드 이며 다른 쓰레드에게 양보를 합니다

yield()와interrupt()를 적절히 사용하면 프로그램의 응답성을 높이고 보다 효율적인 실행이 가능하게 할 수 있습니다.

os스케줄러 통보    yield()는 os스케줄러에 의해 제어되므로 반드시 동작한다고 장담할수 없습니다

 

public void run() {
    String name =th.getName();

    while(!stopped) {
        if(!suspended) {
            System.out.println(name);
            try {
                Thread.sleep(1000);
            } catch(InterruptedException e) {
                System.out.println(name + " - interrupted");
            }
        } else {
            Thread.yield(); // yield() 추가 
        }
    }
    System.out.println(name + " - stopped");
}

 

interrupt()

public void suspend() {
    suspended = true;
    th.interrupt();
    System.out.println(th.getName() + " - interrupt() by suspend()");
}

public void stop() {
    stopped = true;
    th.interrupt();
    System.out.println(th.getName() + " - interrupt() by stop()");
}

Interrypt를 호출 하면 sleep()에서 예외가 발생해 즉시 정지 상태에서 벗어날 수 있으므로 지연 시간을 없앨 수 있습니다.

 

 

join()

자신이 하던 작업을 잠시 멈추고 다른 쓰레드가 지정된 시간동안 작업을 수행하도록 할때 사용합니다.

void join()//작업이 끝날때까지
    void join(long millis)//얼마나 //천분의 일초동안
        void join(long millis, int nanos)

시간을 지정하지 않으면 해당 쓰레드가 작업을 모두 마칠 때까지 기다리게 됩니다. 작업 중 에 다른 쓰레드의 작업이

먼저 수행되어야할 필요가 있을 때 join()을 사용합니다.

 

try{
    thl.join();//현재 실행중인 쓰레드가 쓰레드th1의 작업이 끝날때까지 기다린다.
    }catch(InterruptedException e) {}

 

쓰레드의 동기화 

멀티쓰레드 프로세스의 경우 여러 쓰레드가 같은 프로세스 내의 자원을 공유해서 작업하기 때문에 서로의 작업에 영향을 주게 됩니다.    그래서 도입된 개념이 바로 잠금(lock) 입니다

공유 데이터를 사용하는 코드 영역을 임계 영역으로 지정해놓고, 공유 데이터(객체)가 가지고 있는 lock을 획득한 단 하나의 쓰레드만 이 잉역 내의 코드를 수행할 수 있게 합니다.   해당쓰레드가 임계영역 내의 모든 코드를 수행하고 벗어나서 lock을 반납해야만 다른 쓰레드가 반납한 lock을 획득하여 임계 영역의 코드를 수행할 수 있게 됩니다.

 

한 쓰레드가 진행중인 작업을 다른 쓰레드가 간섭하지 못하도록 막는것을 쓰레드의 동기화 입니다.

 

synchronized를 이용한 동기화

임계 영역을 설정(초기화)하는데 사용됩니다.

    1.메서드 전체를 임계 영역으로 지정  
public synchronized void calcSum(){
    //...
}

2.특정한 영역을 임계 영역으로 지정
synchronized (객체의 참조변수){

    
//...
    }

 

wait()와 notify()

waot(기다리기) notity(통보,알려주기)

 

특정 쓰레드가 객체의 락을 가진 상태로 오랜 시간을 보내지 않도록 하는 것도 중요합니다

 

다른 쓰레드들은 모두 해당 객체의 락을 기다리느라 다른 작업들도 원할히 진행되지 않을 것입니다

이러한 상황을 개선하기 위해 고안된 것이 바로 wait()와 notity()입니다

 

 

wait() , notity(귓속말) , notityAll(방송)

 

  • Object에 정의되어 있습니다
  • 동기화 불록(synchronized)내에서만 사용할 수 있습니다
  • 보다 효율적인 동기화를 가능하게 합니다
class Customer implements Runnable {
    private Table  table;
    private String food;

    Customer(Table table, String food) {
        this.table = table;
        this.food  = food;
    }

    public void run() {
        while(true) {
            try { Thread.sleep(10);} catch(InterruptedException e) {}
            String name = Thread.currentThread().getName();

            if(eatFood())
                System.out.println(name + " ate a " + food);
            else
                System.out.println(name + " failed to eat. :(");
        } // while
    }

    boolean eatFood() { return table.remove(food); }
}

class Cook implements Runnable {
    private Table table;

    Cook(Table table) {    this.table = table; }

    public void run() {
        while(true) {
            int idx = (int)(Math.random()*table.dishNum());
            table.add(table.dishNames[idx]);
            try { Thread.sleep(100);} catch(InterruptedException e) {}
        } // while
    }
}

class Table {
    String[] dishNames = { "donut","donut","burger" };
    final int MAX_FOOD = 6;
    private ArrayList<String> dishes = new ArrayList<>();
    public synchronized void add(String dish) { // synchronized를 추가
        if(dishes.size() >= MAX_FOOD)
            return;
        dishes.add(dish);
        System.out.println("Dishes:" + dishes.toString());
    }

    public boolean remove(String dishName) {
        synchronized(this) {
            while(dishes.size()==0) {
                String name = Thread.currentThread().getName();
                System.out.println(name+" is waiting.");
                try { Thread.sleep(500);} catch(InterruptedException e) {}
            }

            for(int i=0; i<dishes.size();i++)
                if(dishName.equals(dishes.get(i))) {
                    dishes.remove(i);
                    return true;
                }
        } // synchronized

        return false;
    }

    public int dishNum() { return dishNames.length; }
}

class Ex13_14 {
    public static void main(String[] args) throws Exception {
        Table table = new Table(); // 여러 쓰레드가 공유하는 객체

        new Thread(new Cook(table), "COOK").start();
        new Thread(new Customer(table, "donut"),  "CUST1").start();
        new Thread(new Customer(table, "burger"), "CUST2").start();

        Thread.sleep(5000);
        System.exit(0);
    }
}

 

 

 

 

 

 

'JAVA > 객체지향' 카테고리의 다른 글

스트림  (0) 2023.02.26
람다식  (0) 2023.02.25
제네릭,열거형  (0) 2023.02.24
래퍼 클래스,number클래스,문자열을 숫자로 변환  (0) 2023.02.23
String클래스  (0) 2023.02.23