1. 프로세스와 쓰레드

1) 애플리케이션이란?

→ 하나의 프로그램 단위

 

2) 프로세스란?

→ 하드디스크에 저장되어 있다가 메모리에서 실행 중인 프로그램

→ 각 프로세스는 하나하나가 독립된 코드, 스태틱, 스택, 힙 영역을 가짐

 

3) 멀티 프로세스란?

→ 하나의 애플리케이션을 여러 개 실행했을 때 각각의 애플리케이션은 다중 프로세스를 생성

→ 애플리케이션 단위로 멀티태스킹을 할 수 있도록 도와줌

 

4) 쓰레드란?

→ 하나의 프로세스 내부에서 실행되는 하나의 코드 실행 단위

 

5) 싱글쓰레드와 멀티쓰레드

- 싱글쓰레드 : 순차적으로 실행되는 코드

- 멀티쓰레드 : 한 번에 여러 작업을 동시에 수행 → 코드, 스태틱, 힙은 공유, 스택은 쓰레드 하나당 하나씩 생성

 

6) 멀티프로세스와 멀티쓰레드

- 멀티프로세스 : 독립된 프로세스 영역이기 때문에 하나가 오류가 나도 다른 하나는 영향을 받지 않음

   ex) 워드와 엑셀 실행 시 워드가 오류가 나서 종류된다고 해도 엑셀은 영향을 받지 않음

- 멀티쓰레드 : 하나의 프로세스 내에서 여러 개의 쓰레드가 돌아가기 때문에 프로세스 자체에 문제 발생 시 모든 쓰레드가 영향을 받음

 

※ 쓰레드 = 경량 프로세스

→ 멀티쓰레드 프로그램이 멀티프로세스에 비해 자원을 훨씬 더 먹기 때문에 위와 같은 단점에도 불구하고 멀티쓰레드를 사용

 

 

2. 자바의 쓰레드

1) 자바의 쓰레드

- 모든 자바 애플리케이션은 메인쓰레드가 메인메서드를 실행하며 시작

- 메인메서드는 순차적으로 코드 실행, return이나 마지막 코드 실행 후 종료

- 메인쓰레드는 필요에 따라 보조 쓰레드를 추가로 실행 가능

- 작업쓰레드 : 메인쓰레드가 호출한 보조 쓰레드

- 싱글쓰레드에서는 메인쓰레드 종료 시 즉시 프로세스 종료

- 멀티쓰레드에서는 실행중인 쓰레드가 하나라도 남아있다면 프로세스 유지

 

2) 작업쓰레드 생성해보기

- 쓰레드에 대한 정의 시 클래스파일을 쓰레드 개수만큼 생성

- 쓰레드 정의를 목적으로 생성된 클래스는 java.lang.Thread 클래스의 인스턴스를 직접 생성

- Thread 객체 생성, Runnable을 파라미터로 갖는 생성자 호출, Runnable 인터페이스 구현, run() 메서드 작성

 

// 보조쓰레드에서 실행할 내용을 정의하기 위해
// 1. Runnable 인터페이스 구현
public class MultiThread implements Runnable{
	// 2. Runnable 인터페이스의 run() 메서드를 오버라이딩해 실행할 내용 적음
	@Override
	public void run() {
		// 지금까지 코드는 순차적으로 쉬는시간 없이 실행되었음
		// Thread.sleep(밀리초);는 해당 쓰레드실행을 입력한 초만큼 중단
		// 쓰레드 중지는 try~catch 블럭에 반드시 넣어야 함
		try {
			for(int i = 0; i < 500; i++) {
				System.out.println("보조쓰레드 실행 : " + i);
				Thread.sleep(200); // 0.2초
			}
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
}
public class MainThread {

	public static void main(String[] args) {
		// 보조쓰레드 실행을 위해서는 먼저 Thread 객체를 생성해야 함
		
		// 1. Runnable 구현체 생성
		Runnable trd = new MultiThread();
		
		// 2. Thread 클래스의 인스턴스 생성
		// 생성자 호출 시 위의 Runnable 구현체를 파라미터로 넘김
		Thread sTrd = new Thread(trd);
		
		System.out.println("보조쓰레드 준비 완료!");
		
		// 3. 보조쓰레드는 .start()로 호출 가능
		sTrd.start();
		System.out.println("먼저 끝나도 프로그램은 계속 돌아가는 메인메서드");
	}
}

메인쓰레드는 종료되어도 보조쓰레드는 계속 실행되고 있는 것을 확인할 수 있다.

 

- 활용 예시 코드

public class HighChef implements Runnable {

	@Override
	public void run() {
		System.out.println("후임들에게 일하라고 시킵니다.");
		try {
			Thread.sleep(1000);
			mainMenu();
		} catch(Exception e) {}
	}
	
	public void mainMenu() {
		System.out.println("주메뉴를 만들기 시작합니다.");
		try {
			Thread.sleep(5000);
			System.out.println("주메뉴 완성");
		} catch(Exception e) {}
	}
}
public class MiddleChef implements Runnable {

	@Override
	public void run() {
		subMenu();
		boil();
	}
	
	public void subMenu() {
		System.out.println("부찬을 만들기 시작합니다.");
		try {
			Thread.sleep(5000);
			System.out.println("부찬 완성");
		} catch(Exception e) {}
	}
	
	public void boil() {
		System.out.println("국을 끓이기 시작합니다.");
		try {
			Thread.sleep(6000);
			System.out.println("국 완성");
		} catch(Exception e) {}
	}
}
public class LowChef implements Runnable {

	@Override
	public void run() {
		side();
		rice();
		ingredient();
	}
	
	public void side() {
		System.out.println("반찬을 만들기 시작합니다.");
		try {
			Thread.sleep(2000);
			System.out.println("반찬 완성");
		} catch(Exception e) {}
	}
	
	public void rice() {
		System.out.println("밥을 짓기 시작합니다.");
		try {
			Thread.sleep(10000);
			System.out.println("밥 완성");
		} catch(Exception e) {}
	}
	
	public void ingredient() {
		System.out.println("재료 손질을 시작합니다.");
		try {
			Thread.sleep(3000);
			System.out.println("재료 손질 완성");
		} catch(Exception e) {}
	}
}
public class MultiRestaurant {
	public static void main(String[] args) {
		Runnable highR = new HighChef();
		Runnable middleR = new MiddleChef();
		Runnable lowR = new LowChef();
	
		Thread t1 = new Thread(highR);
		Thread t2 = new Thread(middleR);
		Thread t3 = new Thread(lowR);
		
		// 세 명의 요리사가 동시에 작업
		t1.start();
		t2.start();
		t3.start();
	}
}

위와 같이 여러 개의 쓰레드가 생성되어 코드가 순차적으로 실행되지 않고 동시에 여러 작업을 수행하는 것을 확인할 수 있다.

쓰레드 실행은 start() 함수를 이용하여 호출할 수 있다.

 

- Runnable과 Thread 객체를 따로 만드는게 불편하다면 아래와 같이 익명객체를 생성해 사용해도 된다.

Thread thread = new Thread(new Runnable(){
	public void run(){
    	// 해당 쓰레드가 실행할 코드
    }
});

 

 

3) 쓰레드의 우선순위

- 동시성 : 싱글코어 CPU를 이용한 멀티쓰레드는 실제로는 순차적으로 번갈아가며 실행하지만, 속도가 빨라서 우리가 보기엔 동시에 작업이 이뤄지는 것처럼 보임

- 병렬성 : 멀티코어 CPU를 이용한 멀티쓰레드는 진짜 동시에 여러 작업 수행

 

4) 쓰레드의 공유객체 문제

public class ThreadNotSafety extends Thread {

	// static으로 선언된 변수는 자동으로 0 할당
	static int share;
	
	public static void main(String[] args) {
		ThreadNotSafety t1 = new ThreadNotSafety();
		ThreadNotSafety t2 = new ThreadNotSafety();

		t1.start();
		t2.start();
	}
	
	@Override
	public void run() {
		for(int cnt = 0; cnt < 10; cnt++) {
			System.out.println(share++);
			
			try {Thread.sleep(10);}
			catch(Exception e) {}
		}
	}
}

위와 같이 순차적으로 진행되지 않고 2개의 쓰레드가 static 변수에 동시에 접근하여 의도와 다른 결과가 나올 확률이 있음

※ Race Conditon : 공용 자원을 두고 멀티쓰레드나 멀티프로세스가 경쟁상태에 돌입

 

∴ 특정 로직에 대해서는 동시에 하나의 쓰레드만 접근할 수 있도록 처리할 필요가 있음 → synchronized 키워드

public synchronized void 메서드(){
	// 실행문
}

위 메서드는 동시에 하나의 쓰레드만 접근 가능, 다른 메서드가 접근할 준비가 되어도 대기하기 때문에 공유객체문제 해결 가능

 

public void 메서드(){
	// 실행코드
    synchronized(공유자원){
    	// 단 하나의 쓰레드 단위로만 실행 가능한 실행코드
    }
    // 실행코드
}

위와 같이 특정 코드블럭만 임계영역으로 설정하는 것도 가능하다.

 

public class ThreadSafety extends Thread {
	
	static int share;
	
	public static void main(String[] args) {
		ThreadSafety t1 = new ThreadSafety();
		ThreadSafety t2 = new ThreadSafety();
	
		t1.start();
		t2.start();
	}
	
	// 쓰레드 안전을 위해서 사용하는 키워드
	public synchronized static void sharePlus() {
		System.out.println(share++);
	}
	
	@Override
	public void run() {
		for(int cnt = 0; cnt < 10; cnt++) {
			sharePlus(); // 동시성 제어가 되는 메서드로 1씩 증가
			try {Thread.sleep(1);}
			catch(Exception e) {}
		}
	}
}

위와 같이 동시에 접근해도 제어가 되어 1씩 잘 증가되는 것을 확인할 수 있다.

'네트워크캠퍼스 > JAVA' 카테고리의 다른 글

객체지향적 코드 작성  (0) 2024.01.29
객체지향 정리  (0) 2024.01.25
자바 API  (0) 2024.01.18
자바의 예외처리전략  (0) 2024.01.18
예외처리  (0) 2024.01.17

+ Recent posts