Thread

dev_hnbm·2023년 11월 6일
0

대덕인재개발원

목록 보기
12/30

💿 Thread

🤔 Thread 공부하기 전에 프로세스부터 알고 넘어가기

  • 운영체제의 관점에서 실행 중인 독립적인 프로그램이다.
  • 메모장, 크롬, 이클립스를 실행했다면?
    • 3개의 어플리케이션을 실행한 상태이며, 각 어플리케이션은 별도의 독립적인 프로세스가 존재하기 때문에 멀티 태스킹이 가능하다.
  • 자원스레드로 구성되어 있다.

🤷‍♀️ Thread란?

  • 명령어들의 집합이다.
  • 프로세스 내에서 실제 작업을 수행하며, 모든 프로세스는 최소 1개 이상의 스레드를 가진다.
    • 대부분 이를 메인 스레드라고 하며, 부가적인 스레드를 생성하여 병렬 작업을 수행할 수 있다.

single thread process

  • 자원 + thread

multi thread process

  • 자원 + 여러 thread

multi process, multi thread

  • 운영체제가 하나의 결과를 위해 여러 작업을 동시에 처리하는 개념은 비슷하다.
    • 프로세스:스레드 = 공장:일꾼 = 🏭:👩‍🔧
      • 🏭👩‍🔧🏭 vs 🏭👩‍🔧👩‍🔧
        → 일반적으로 하나의 프로세스를 생성하는 것보다 하나의 스레드를 생성하는 것이 더 효율적이다.
  • 멀티 프로세스: 독립적인 실행 단위이며 각각이 자체 메모리를 가지고 동작
  • 멀티 스레드: 하나의 프로세스 내에서 여러 스레드가 메모리를 공유하며 동작
  • 프로세스 간 통신이 필요한 경우 멀티 프로세스가 적합하며, 자원 공유와 효율적인 작업 분배가 필요한 경우 멀티 스레드가 적합하다.



✔ multi thread의 장단점

장점

  • 자원을 효율적으로 사용할 수 있다.
    • 여러 스레드가 동시에 실행되어 CPU 시간 및 메모리 등 자원을 효과적으로 활용할 수 있다.
  • 사용자에 대한 응답성이 향상된다.
    • 하나의 스레드가 블록되어 작업을 수행하는 동안에도 다른 스레드들이 실행되므로 전체적으로 응답성이 향상된다.
  • 작업이 분리되어 코드가 간결해진다.
    • 각 스레드가 특정 작업에 집중할 수 있어 코드가 간결해지고 유지보수가 쉬워진다.

단점

  • 동기화(synchronization)에 주의해야 한다.
    • 자원과 2개의 A 스레드, B 스레드가 있을 때
      • CPU → A 스레드에 시간을 할당하고, 주어진 시간이 끝나면 중간에 멈췄다가,
        B 스레드에 시간을 할당하고, 주어진 시간이 끝나면 다시 A 스레드로... 왔다리갔다리 한다.
      • 이때, CPU 속도가 빠르기 때문에 동시에 실행되는 것처럼 보인다.
        ⛔⛔ 처리 도중 자원의 데이터가 변경되면 A 스레드와 B 스레드는 다른 데이터를 처리하게 된다.
  • 교착 상태에 주의해야 한다.
    • 교착 상태란, 둘 이상의 프로세스나 스레드가 서로 상대방의 작업이 끝나기를 기다리며 무한히 대기하는 상태다.
      • 각 스레드가 효율적으로, 고르게 실행될 수 있게 해야 한다.




👩‍💻 스레드 구현하기

1. Thread 클래스 상속

  1. Thread 클래스를 상속한 class를 작성한 후, 인스턴스 생성
    • run() 메소드를 재정의하여 코드 작성
      • 재정의한 내용이 스레드를 처리할 때 실행
  2. 인스턴스의 start() 메소드를 호출하여 실행

    java는 단일 상속만을 지원하기 때문에, 이미 다른 클래스를 상속받고 있을 때에는 이 방법을 사용할 수 없다.

2. Runnable 인터페이스 구현

  1. Runnable 인터페이스를 구현한 class를 작성한 후, 인스턴스 생성
    • run() 메소드를 재정의하여 코드 작성
      • 재정의한 내용이 스레드를 처리할 때 실행
  2. 인스턴스를 Thread 클래스의 인스턴스를 생성할 때 생성자의 인수값으로 넘겨줌
  3. 인스턴스의 start() 메소드를 호출하여 실행

    ❗ Runnable 인터페이스를 구현할 경우, 단일 상속과는 상관없이 멀티 스레드를 만들 수 있다.
        👉 인터페이스는 하나의 클래스가 여러개를 구현할 수 있기 때문에!
package kr.or.ddit.basic;

public class ThreadTest14 {
	public static void main(String[] args) {

		MyThread thread1 = new MyThread();
		thread1.start();

		MyRunner runner = new MyRunner();
		Thread thread2 = new Thread(runner);
		thread2.start();

	}
}

// 1. Thread 클래스 상속
class MyThread extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println("*");

			try {
				Thread.sleep(1000); // 단위는 밀리세컨드 (1초 -> 1000)
			} catch (InterruptedException e) {
			}
		}
	}
}

// 2. Runnable 인터페이스 구현
class MyRunner implements Runnable {
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println("#");

			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
			}
		}
	}
}

😶 Thread.sleep()을 try-catch문으로 처리하는 이유

  • sleep() 메소드는 스레드를 지정된 시간동안 일시중지 시키는데, 중간에 다른 스레드가 일시중지 된 스레드를 interrupt() 메소드를 통해 깨우려고 할 때 InterruptedException이 발생할 수 있다.
  • 따라서 코드의 안정성과 적절한 대응을 하기 위해 예외 처리를 해주는 것이 좋다.




📌 start()

run() 메소드를 재정의했는데 start() 메소드를 호출하는 이유

start() 메소드를 호출하면 새로운 스레드가 생성되고, 내부적으로 해당 스레드가 실행되는데, 그 안에서 run() 메소드가 호출되기 때문이다. 직접적으로 run() 메소드를 실행할 경우 일반적인 단순 메소드 호출이 되기 때문에 single thread와 동일하게 처리된다. main() 메소드의 실행과는 병렬적으로 동작되어 main() 메소드가 실행되는 동안 다른 스레드를 생성하고 실행할 수 있다.



🛠 데몬스레드

  • 일반 스레드의 보조 역할이며, 일반 스레드가 모두 종료되면 자동으로 종료된다.
  • 일반 스레드와 만드는 방법은 동일함
    • 스레드 실행 전 (start() 호출 전) 데몬 스레드라고 명시해야 하고, start() 이후 설정시 Exception 발생
      • setDaemon() → true: 데몬스레드, false: 일반스레드
    • 데몬 스레드 확인
    • isDaemon() → true: 데몬스레드, false: 일반스레드




⚙ 스레드의 상태

상태설명
NEW스레드 객체 생성 후 start() 호출 전
RUNNABLE실행 중 / 실행 가능한 상태
BLOCKED, WAITING, TIMED_WAITING일시정지된 상태 (sleep(), join(), suspend(), wait(), I/O block), 주어진 시간이 끝나면 일시정지를 마치고 대기열로 감 (resume(), notify(), interrupt())
TERMINATED스레드 작업 종료 및 스레드 객체 소멸





⚙ 실행제어

suspend(): 스레드 일시 정지
resume(): suspend()에 의해 일시 정지 상태인 스레드를 실행 대기 상태로
join(): 지정된 시간동안 스레드 실행, 지정된 시간이 지나거나 작업 종료시 join()을 호출한 스레드로 돌아가서 실행
interrupt(): sleep(), join()에 의해 일시 정지된 스레드를 실행 대기 상태로, InterruptException 발생시 일시 정지 상태를 벗어남
sleep(): 지정된 시간동안 스레드 일시 정지, 지정된 시간이 지나면 자동으로 실행 대기 상태
stop()

  • 스레드 즉시 종료 → 사용하던 자원을 정리하지 못하고 즉시 종료되어 교착 상태에 빠지기 쉽기 때문에 사용하지 않는 편이 좋음
    • 교착 상태: 멀티스레드나 멀티프로세스 환경에서 둘 이상의 프로세스나 스레드가 서로 상대방의 작업이 끝나기만을 기다리며 진행하지 못하는 상태
    • 즉, 각 프로세스나 스레드는 자원을 보유하고 있고, 서로가 필요로 하는 자원을 상대방이 가지고 있는 상황에서 무한정 대기하게 되는 상태
      yield(): 실행 중 다른 스레드에게 양보 후 실행 대기 상태로



⚖ 자원 공유

  • 스레드끼리 동일한 자원을 공유하려면 static으로 변수 선언 → method 영역에 저장
    • 해당 변수의 종류와 사용 방식에 따라 다르지만, 메모리 영역을 많이 차지할 수도 있고 어디서 어떻게 변하는지 모르기 때문에 되도록 적게 쓰는게 좋음
  • 스레드에서 공유할 객체가 1개 있을 때
    • 객체의 참조값만 저장하면 공유 가능
    • ex) 경주마 프로그램의 GameState 클래스의 horseArr
      • main 메소드에서 만들어진 horseArr를 넘겨주고, GameState 클래스가 받음 (참조값)

공통으로 사용할 객체의 참조값 저장하는 방법

  1. getter setter 이용
public void setSd(ShareData sd) {
  this.sd = sd;
}
  1. 생성자 이용
public PrintPIThread(ShareData sd) {
  this.sd = sd;
}

동기화

Vector, Hashtable 등과 같이 예전부터 존재하던 Collection 객체들은 내부에 동기화 처리가 되어 있다. 새로 구성된 Collection들은 동기화 처리가 되어 있지 않다. 동기화가 필요한 프로그램에서 이런 Collection들을 사용하려면 동기화 처리 후 사용해야 한다.



✔ wait(), notify()

0개의 댓글