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 |