1. 스레드(Thread)

  • 프로세스: 실행중인 프로그램이 실행되면 OS로부터 메모리를 할당받아 프로세스 상태가 된다
    = 공장
  • 스레드: 하나의 프로세스는 하나 이상의 스레드를 가지게 되고, 실제 작업을 수행하는 단위는 스레드이다
    = 일꾼

스레드의 종류

1) 싱글 스레드 프로세스

  • 공장에 일꾼이 한 명인 것

2) 멀티 스레드 프로세스

  • 일꾼이 n명인 것

  • 장점
    시스템 자원을 효율적으로 사용할 수 있다
    사용자에 대한 응답성이 향상된다
    작업이 분리되어 코드가 간결해진다

  • 단점
    동기화에 주의해야 한다
    교착상태가 발생하지 않도록 주의해야 한다
    각 스레드가 효율적으로 고르게 실행될 수 있게 한다
    실행되지 못하는 상태인 기아상태가 발생하지 않도록 주의해야 한다

  • 여러 스레드가 동시에 수행되는 프로그래밍, 여러 작업이 동시에 실행되는 효과

  • 스레드는 각각 자신만의 작업 공간을 갖는다

  • 각 스레드 사이에서 공유하는 자원이 있을 수 있다

  • 여러 스레드가 자원을 공유하여 작업이 수행되는 경우, 서로 자원을 차지하려는 race condition이 발생할 수 있다

  • 자원을 차지하려는 경쟁이 발생하는 부분을 critical section(임계영역)이라고 한다

  • 그에 대한 동기화를 구현하지 않으면 오류가 발생할 수 있다
    순차적 수행, 한 스레드가 사용할 때 다른 스레드가 사용할 수 없도록 락을 거는 것

    스레드의 I/O 블락킹

    • 싱글 스레드 프로세스에서는 스레드 실행 중에 사용자의 입력을 받는 구간이 있다면 스레드가 아무 일도 하지 않고 입력을 기다린다
    • 멀티 스레드에서는 입력을 기다린다면 다른 스레드의 작업이 실행된다

스레드 생성

1) Thread 클래스 상속해서 만들기

class MyThread extends Thread{
	
	public void run() {
		
		int i;
		for(i = 0; i<200; i++) {
			System.out.print(i + "\t");
		}
	}
}

public class ThreadTest {

	public static void main(String[] args) {

		System.out.println(Thread.currentThread());
		MyThread th1 = new MyThread();
		th1.start();
		
		MyThread th2 = new MyThread();
		th2.start();
	}

}

2) Runnable 인터페이스 구현하여 만들기

public static void main(String[] args) {
    //스레드 1 : 메인 스레드는 스레드 두개 생성하고 스타트하면 끝
    System.out.println(Thread.currentThread() + " start");
    MyThread runnable = new MyThread();
    Thread th1 = new Thread(runnable); // runnable객체를 입력받을 수 있다
    Thread th2 = new Thread(runnable);
    th1.start();
    th2.start();
    
    System.out.println(Thread.currentThread() + " end");
}
  • 간단하게 돌리는 방법
Runnable run = new Runnable() { 
//바로 익명 객체를 불러올 수 있다 - 메서드 구현해야함
    @Override
    public void run() {
        System.out.println("run");
    }
};
run.run();

Runnable 인터페이스를 구현하여 쓰는 것이 코드가 길어지긴 하지만 다른 클래스를 상속받아야 할 경우가 있을 수 있기 때문에 주로 사용된다

스레드 Status

2. 스레드의 종류

사용자 스레드

  • main 스레드 : main 메서드의 코드를 수행하는 스레드
  • 데몬 스레드가 아닌 것들

데몬 스레드

  • 보조 스레드

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

3. 스레드의 메서드

1) start()와 run()

  • 인터페이스/추상클래스에서 구현한 메서드는 run()
  1. main 메서드에서 start를 호출하면 satrt가 새로운 호출 스택을 생성하고
  2. 새로운 호출 스택에 run을 올리고 start 종료된다
  • 즉 각각의 스레드(main과 run)는 자신만의 호출 스택을 갖게 되어 서로 독립적인 수행이 가능한 것이다

2) priority

여러 스레드를 start하면 실행된 순서대로 스레드가 실행되지 않을 수 있다
실행 순서는 OS의 스케쥴러가 결정한다
스케쥴러에게 사용자가 원하는 실행 순서를 제안하는 방법으로 우선순위를 둔다

  • Thread.MIN_PRIORITY(=1) ~ Thread.MAX_PRIORITY(=10)
  • 디폴트 우선순위 : Thread.NORMAL_PRIORITY(=5)
  • 우선 순위가 높은 Thread가 CPU의 배분을 받을 확률이 높다
  • setPriority()/getPriority()
  • Thread 우선순위 예제
PriorityThread pt1 = new PriorityThread();
PriorityThread pt2 = new PriorityThread();
PriorityThread pt3 = new PriorityThread();
pt1.setPriority(Thread.MIN_PRIORITY);
pt2.setPriority(Thread.NORM_PRIORITY);
pt3.setPriority(Thread.MAX_PRIORITY);
pt1.start();
pt2.start();
pt3.start();
원하는 스레드에 우선순위를 부여할 수 있다

3) sleep()

  • 현재 스레드를 지정된 시간동안 멈추게 한다
  • 자기 자신에 대해서만 동작한다
    스레드.sleep() 으로 사용하지만 스레드에 어떤 스레드가 들어가도 현재 자신 스레드에 sleep이 적용된다
  • 1000 = 1초
  • 예외처리를 해야한다
    InterruptedException (슬립을 깨우는 것)
    따로 메서드를 만들어서 그 안에 예외처리를 한다

4) interrupt()

  • 대기 상태인 스레드를 실행 대기 상태로 만든다
  • isterrupted : 상태 반환
  • interrupted : 상태 반환하고 false로 초기화
    현재 스레드에 대해서 static으로 사용하는 메서드

5) suspend()

  • 일시정지
  • <> resume()

6) resume()

  • 재개
  • <> suspend()
  • 일시정지된 스레드를 실행 대기 상태로 만든다

7) stop()

  • 완전 종료

suspend, resume, stop deprecated 됨
일시정지 상태와 정지 상태를 boolean으로 생성하고 반복문에 적용하는 방법으로 구현

8) join()

  • 동시에 두 개 이상의 스레드가 실행될 때 다른 스레드의 결과를 참조하여 실행해야 하는 경우 join() 함수 사용
  • 지정된 시간동안 특정 스레드가 작업하는 것을 기다린다
    일정 시간동안 or 끝날 때까지
  • join함수를 호출한 스레드가 non-runnable 상태로 돌아간다
  • 다른 스레드의 수행이 끝나면 runnable 상태로 돌아온다

public class JoinTest extends Thread{
    int start;
    int end;
    int total;

    public JoinTest(int start, int end) {
        this.start = start;
        this.end = end;
    }

    public void run() {
        int i;
        for (i = start; i <= end; i++) {
            total += i;
        }
    }

    public static void main(String[] args) {

        System.out.println(Thread.currentThread() + "start");
        JoinTest jt1 = new JoinTest(1, 50);
        JoinTest jt2 = new JoinTest(51, 100);

        jt1.start();
        jt2.start();
		//원래는 메인이 jt1,2가 종료되기 전에 종료되어서 jt1,2가 완전히 실행되지 않고 종료된다
        //메인에서 join을 사용하면 메인은 수행을 안한다 jt1, 2가 종료될 때까지 기다린다
        try {
            jt1.join();
            jt2.join();
        } catch (InterruptedException e) { //jt1,2의 수행이 영원히 종료되지 않을 경우 메인으로 돌아오기 위해
            throw new RuntimeException(e);
        }

        int lastTotal = jt1.total + jt2.total;

        System.out.println("jt1.total = " + jt1.total);
        System.out.println("jt2.total = " + jt2.total);

        System.out.println("lastTotal = " + lastTotal);

        System.out.println(Thread.currentThread() + "end");
    }
}

9) yield()

  • 남은 시간을 다음 스레드에 양보하고 자신은 실행 대기한다
  • 다른 스레드에 사용할 수 업고 메서드를 호출하는 해당 스레드에서만 사용할 수 있다

스레드 종료하기

  • 스레드를 종료할 때 사용한다
  • 무한반복의 경우 while(flag)의 flag 변수 값을 false로 바꾸어 종료한다
  • 스레드 종료하기 예제
public class TerminateThread extends Thread{

    private boolean flag = false;
    int i;

    public TerminateThread(String name){
        super(name);
    }

    public void run(){


        while(!flag){ //while(true)로 두지 말고 플래그를 이용해서 수정할 수 있다
            try {
                sleep(100);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        System.out.println( getName() + " end" );

    }

    public void setFlag(boolean flag){
        this.flag = flag;
    }


    public static void main(String[] args) throws IOException {

        TerminateThread threadA = new TerminateThread("A");
        TerminateThread threadB = new TerminateThread("B");
        TerminateThread threadC = new TerminateThread("C");

        threadA.start();
        threadB.start();
        threadC.start();

        int in;
        while(true){
            in = System.in.read();
            if ( in == 'A'){
                threadA.setFlag(true); //a 스레드 종료
            }else if(in == 'B'){ 
                threadB.setFlag(true); //b 스레드 종료
            }else if( in == 'C'){
                threadC.setFlag(true); //c 스레드 종료
            }else if( in == 'M'){
                threadA.setFlag(true); //모든 스레드 종료
                threadB.setFlag(true);
                threadC.setFlag(true);
                break;
            }else{
                System.out.println("type again");
            }
        }

        System.out.println("main end");

    }
}

4. 스레드의 동기화

  • 멀티 스레드 프로세스에서는 다른 스레드의 작업에 영향을 미칠 수 있다
    교착 현상

  • 진행중인 작업이 다른 쓰레드에게 간섭받지 않게 하려면 동기화가 필요하다

    간섭받지 않아야 하는 문장을 임계 영역으로 설정한다

  • 임계 영역은 락(자물쇠)을 얻은 단 하나의 스레드만 출입 가능

synchronized

  • 임계영역을 설정하는 키워드
  1. 특정한 영역을 임계 영역으로 지정
  2. 메서드 전체를 임계영역으로 지정

임계 영역은 최소화 하는 것이 좋음
2번 방법으로 하는 것이 좋음

  • 출금하는 중에는 다른 스레드의 작업이 수행될 수 없도록 함

동기화를 많이 사용하면 효율이 떨어진다.

효율을 높이는 방법으로 wait()과 notify()가 사용된다

1) wait()

  • 기다리기
  • Object 클래스에 정의되어 있고, 동기화 블록 내에서만 사용 가능
  • 객체의 락을 풀고 스레드를 실행 대기 상태에 등록한다

2) notify()

  • 통보, 알려주기
  • 실행 대기 상태의 스레드 중 하나를 깨운다
  • notifyAll() : 실행 대기 상태의 모든 스레드를 깨운다

예제

  • 요리사는 테이블에 음식을 추가, 손님은 테이블의 음식을 소비
  1. 요리사와 손님이 같은 객체인 테이블을 공유하므로 동기화가 필요함
  • > table의 add()와 remove()를 synchronized로 동기화 해야함
  1. 손님이 테이블에 음식이 없어서 락을 건 채로 계속 기다리는 상황
    요리사는 락이 없어서 음식을 추가할 수 없다
  • > 음식이 없을 때는 wait()으로 손님이 락을 풀고 기다리도록 함

  • > 손님이 음식을 먹으면 요리사에게 notify()로 통보

  • > 음식이 추가되면 손님에게 알려 다시 락을 획득하도록 함

  • 결과

profile
안녕하세요. Chat JooPT입니다.

0개의 댓글