[Java] Thread

Jeini·2023년 3월 14일
1

☕️  Java

목록 보기
56/59
post-thumbnail

💡 병렬화 할 때 고려해야 할 것들


  • 메모리의 속도
  • CPU 캐시 메모리
  • 디스크
  • 네트워크
  • 컨넥션

❗️ 순차적 실행이 병렬실행 보다 빠른 경우도 있다. 동시 실행에 따르는 오버헤드가 없고, 단일 CPU 알고리즘은 하드웨어 작업에 더 친화적일 수 있기 때문이다.

💡 프로세스(Process)


✔️ 단순히 실행중인 프로그램

즉, 사용자가 작성한 프로그램이 운영체제에 의해 메모리 공간을 할당받아 실행 중인 것을 의미.
이러한 프로세스는 프로그램에 사용되는 데이터와 메모리 등의 자원 그리고 스레드로 구성됨.

📎 프로세스 특징

  • 각각의 프로세스는 메모리 공간에서 독립적으로 존재한다.
  • 각각의 프로세스는 위 그림과 같이 자신만의 메모리 구조를 가진다.
  • 프로세스 A, B, C가 있을 경우 각각 프로세스는 모두 같은 구조의 메모리 공간을 가진다.
  • 독립적인 만큼 다른 프로세스의 메모리 공간에 접근할 수 없다.

✏️ Mac 활성상태보기에서 프로세스 보기

각각의 프로세스들은 자신만의 메모리 영역을 확보한 상태로 실행되고 있다는 것을 알 수 있다.

💡 IPC


✔️ 프로세스 간의 통신하는 방법

  • 프로세스 A에서 프로세스 B를 직접 접근할 수 없기 때문에 프로세스 간의 통신을 하는 특별한 방식이 필요하다. ➡️ 메일슬록(mailslot), 파이프(pipe)

  • 프로세스는 독립적인 메모리 공간을 지니기 때문에 IP를 통하지 않고 통신할 수 없다.

  • 프로세스가 여럿이 병렬적으로 실행되기 위해서는 필연적으로 컨텍스트 스위칭이 발생할 수 밖에 없다. 그것을 해결할 수 있는 것이 Thread이다.

💡 스레드(Thread)


✔️ 프로세스 내에서 실제로 작업을 수행하는 주체를 의미

❗️ 특정시간에 Thread1 특정시간에 Thread2 이렇게 시간을 쪼개서 각각의 스레드가 실행됐다 안됐다를 반복하면서 실행하게 된다.

  • 스레드는 하나의 프로그램 내에 존재하는 여러 개의 실행 흐름을 위한 모델이다.

  • 우리가 생각하는 프로그램이 실행되기 위해서 하나의 실행흐름으로 처리할 수도 있지만 다수의 실행흐름으로 처리할 수도 있다.

  • 프로세스와 별개가 아닌 프로세스를 구성하고 실행하는 흐름이다.

  • 모든 프로세스에는 한 개 이상의 스레드가 작업을 수행한다.

  • 두 개 이상의 스레드를 가지는 프로세스를 멀티스레드 프로세스(multi-threaded process)라고 한다.

✏️ 메모리 공간에서의 스레드

프로세스와 프로세스의 전환보다, 스레드와 스레드가 전환하는 비용이 더 적다.

💡 스레드의 상태(state of thread)


  • NEW
    : 스레드가 생성되고 아직 start() 가 호출되지 않은 상태

  • RUNNABLE
    : 실행 중 또는 실행 가능한 상태

  • BLOCKED
    : 동기화 블럭에 의해서 일시정지된 상태(Lock이 풀릴 때까지 기다리는 상태)

  • WAITING, TIMED_WAITING
    : 스레드의 작업이 종료되지는 않았지만 실행가능하지 않은(unrnnable) 일시정지 상태.
    : TIMED_WAITING은 일시정지시간이 지정된 경우를 의미.

  • TERMINATED
    : 스레드의 작업이 종료된 상태

  1. 스레드를 생성하고 start() 를 호출 ➡️ 실행대기열에 저장되어 자신의 차례가 될 때까지 기다림

    ❗️ 실행대기열은 큐(Queue)와 같은 구조

  2. 실행대기상태에 있다가 자신의 차례가 되면 실행상태가 됨 (실행대기열에 먼저 들어온 스레드가 실행)

  3. 주어진 실행시간이 다 되거나 yield() 를 만나면 다시 실행대기상태가 되고 다음 차례의 스레드가 실행상태가 됨

    • 실행 중에 suspend(), sleep(), wait(), join(), I/O block 에 의해 일시정지상태가 될 수 있음.

    • 사용자의 입력을 기다리는 경우를 예
      : 일시정지 상태에 있다가 사용자가 입력을 마치면 다시 실행대기상태가 됨

    ❗️ I/O block: 입출력작업에서 발생하는 지연상태

  4. 지정된 일시정지시간이 다되거나(time-out), notify(), resume(), interrupt() 가 호출되면 일시정지상태를 벗어나 다시 실행대기열에 저장되어 자신의 차례를 기다리게 됨.

  5. 실행을 모두 마치거나 stop() 이 호출되면 스레드는 소멸된다.

❗️ 꼭 번호의 순서대로 스레드가 수행되는 것은 아님

💡 스레드 VS 프로세스


  • 스레드는 프로세스 안에 존재하는 실행흐름이다.

  • 스레드는 프로세스의 heap, data(static), code 영역등을 공유한다.

  • 스레드는 stack 영역을 제외한 메모리 영역을 공유한다.

  • 스레드가 code영역을 공유하기 때문에 프로세스 내부의 스레드들은 프로세스가 가지고 있는 함수를 자연스럽게 모두 호출할 수 있다.

  • 스레드는 IPC없이도 스레드 간의 통신이 가능하다.

  • A, B 스레드는 통신하기 위해 heap영역에 메모리 공간을 할당하고, 두 스레드가 자유롭게 접근할 수 있다.

  • 스레드는 프로세스처럼 스케쥴링의 대상이다. 이 과정에서 컨텍스트 스위칭이 발생한다. 하지만 스레드는 공유하고 있는 메모리 영역 덕분에 컨텍스트 스위칭 때문에 발생하는 오버헤드가 프로세스에 비해 작다.

❗️ 스케쥴링
: 멀티프로세스 운영체제에서 프로세스의 CPU 할당 순서 및 방법을 결정짓는 것

❗️ 컨텍스트 스위칭
: 동작중인 프로세스가 바뀔 때 프로세스는 현재 자신의 상태(context 정보)를 일단 보존한 후, 새롭게 동작 개시하는 프로세스는 이전에 보존해 두었던 자신의 컨텍스트 정보를 다시 복구하는 이런 현상을 의미한다.

➡️ 스레드의 컨텍스트 정보는 프로세스보다 적기 때문에 스레드의 컨텍스트 스위칭은 가볍게 행해지는 것이 보통이다. 하지만, 실제로 스레드와 프로세스의 관계는 JVM구현에 크게 의존한다.

  • 참고로 플랫폼이 같아도 JVM의 구현방법에 따라 프로세스와 스레드의 관계는 달라질 수 있다.

💡 스레드의 생성과 실행


✔️ Thread 클래스를 상속받는 방법
✔️ Runnable 인터페이스를 구현하는 방법

두 방법 모두 스레드를 통해 작업하고 싶은 내용을 run() 메서드에 작성하면 된다.

✔️ start()
: 스레드 실행 할 준비를 해주고, run() 를 실행 해 준다.
: 흐름이 하나 더 생긴다.

📎 Thread 클래스를 상속받는 방법

class Xxx extends Thread {
	pubilc void run() {
    	// 동시에 실행될 코드 작성
    }
}
Xxx x = new Xxx();
x.start();

✏️ 단일 스레드(main Thread) 실행 예시

public class MyThreadExam {
    public static void main(String[] args) {
        String name = Thread.currentThread().getName(); // 현재 스레드가 갖고있는 이름값
        System.out.println("thread name: " + name);
        System.out.println("start!");
        // 1초마다 *를 10번 출력하는 프로그램을 작성하시오.
        for(int i = 0; i < 10; i++) {
            System.out.print("*");
            try {
                Thread.sleep(1000); // 1초마다 쉰다.
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 1초마다 +를 10번 출력하는 프로그램을 작성하시오.
        for(int i = 0; i < 10; i++) {
            System.out.print("+");
            try {
                Thread.sleep(1000); // 1초마다 쉰다.
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("end!");
    }
}
thread name: main
start!
**********++++++++++end!

동시에 진행하지 않고 *가 끝나야 +가 나온다.

✏️ 멀티 스레드 실행 예시

✔️ MyThread

// 1. Thread 클래스를 상속받는다.
public class MyThread extends Thread {
    private String str;

    public MyThread(String str) {
        this.str = str;
    }

    // 2. run() 메서드를 오버라이딩 한다.
    // 동시에 실행시키고 싶은 코드를 작성한다.
    @Override
    public void run() {
        for(int i = 0; i < 10; i++) {
            System.out.print(str);
            try {
                Thread.sleep(1000); // 1초마다 쉰다.
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

✔️ MyThreadExam

public class MyThreadExam {
    public static void main(String[] args) {
        String name = Thread.currentThread().getName(); // 현재 스레드가 갖고있는 이름값
        System.out.println("thread name: " + name);
        System.out.println("start!");

        MyThread mt1 = new MyThread("*");
        MyThread mt2 = new MyThread("+");

        // 3. Thread는 Start() 메서드를 실행한다.
        mt1.start(); // 새로운 흐름이 생긴다.
        mt2.start();

        System.out.println("end!");
    }
}
thread name: main
start!
*+end!
*+*+*+*+*+*+*++*+*

❗️ 스레드를 배우기 전에는 main() 메서드가 끝나면 종료되었지만 스레드를 배운 이후에는 모든 스레드가 종료되었을 때 프로그램이 종료된다는 것을 알아둬야 한다.

📎 Runnable 인터페이스를 구현하는 방법

Runnable 은 스레드가 아닌 인터페이스이기 때문에 Start() 메서드가 없다.

그래서 Thread가 Runnable을 가지도록 만들어야 한다.

class xxx implements Runnable {
	public void run() {
    	// 동시에 실행 될 코드 작성
    }
}
Xxx x = new Xxx();
Thread t = new Thread(x);
t.start;

✏️ MyRunnable


// 1. Runnable 인터페이스를 구현한다.
public class MyRunnable implements Runnable {
    private String str;

    public MyRunnable(String str) {
        this.str = str;
    }

    // 2. run() 메서드를 오버라이딩 한다.
    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        System.out.println("---" + name);

        for(int i = 0; i < 10; i++) {
            System.out.print(str);
            try {
                Thread.sleep(1000); // 1초마다 쉰다.
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

✏️ MyRunnableExam

public class MyRunnableExam {
    public static void main(String[] args) {
        String name = Thread.currentThread().getName(); // 현재 스레드가 갖고있는 이름값
        System.out.println("thread name: " + name);
        System.out.println("start!");

        MyRunnable mr1 = new MyRunnable("*");
        MyRunnable mr2 = new MyRunnable("+");

        // 3. Thread 인스턴스를 생성하는데, 생성자에 Runnable 인스턴스를 넣어준다.
        Thread th1  = new Thread(mr1);
        Thread th2 = new Thread(mr2);

        // 4. Thread가 가지고 있는 start() 메서드를 호출한다.
        th1.start();
        th2.start();

        System.out.println("end!");
    }
}

❗️ 실행하다보면 스레드는 서로가 자원을 획득해서 서로가 빨리 실행하고 싶어한다. 그러다보니 실행되는 결과가 항상 똑같지가 않다.


References
: https://mer1.tistory.com/33
: http://www.tcpschool.com/java/java_thread_concept
: https://www.youtube.com/channel/UChWUWqURDfGFHpCIeLO8jZA
: https://velog.io/@matcha_/%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B5%AC%EC%A1%B0%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90

profile
Fill in my own colorful colors🎨

0개의 댓글