Information Security Study

240124 자바(프로세스와 쓰레드) 본문

네트워크 캠퍼스/JAVA

240124 자바(프로세스와 쓰레드)

gayeon_ 2024. 1. 25. 19:08

프로세스와 쓰레드

 

어플리케이션이란?

하나의 프로그램 단위를 어플리케이션이라고 부른다.

예를들어 우리가 실행하는 모든 프로그램 하나 하나가 어플리케이션이라고 할 수 있다.


프로세스란?

하드디스크에 저장되어있는 하나의 단위 프로그램이 실행되었을 때

메모리에서 해당 프로그램을 실행시키기 시작하고 이것을 프로세스라고 부른다.

 

 

위에서는 23 + 6 + 109개의 프로세스가 돌아가고 있다고 볼 수 있다.

 

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

 

 

 

 

 

 

 

멀티 프로세스란?

 

위 사진에서 크롬에는 (8)이 붙어있다.

이렇게 하나의 어플리케이션을 여러 개 실행하면 각각의 어플리케이션이 다중 프로세스를 만든다.

 

 

 

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

바로 멀티 프로세스라고 할 수 있다.

 

 

쓰레드란?

대부분의 프로그램은 메인스레드가 메인메서드를 호출하며 시작한다.

쓰레드라는 단어는 “줄 한 가닥”을 의미한다.

실행코드가 한 줄로 늘어진 것을 보고 그렇게 이름지어진 것이다.

쓰레드는 하나의 프로세스 내부에서 실행되는 하나의 코드 실행 단위다.


 

 

싱글 쓰레드와 멀티 쓰레드 프로그램의 차이와 특징

우리가 여태까지 작성해온 프로그램은 “싱글 스레드” 프로그램으로

모든 코드가 순차적으로 실행되어왔다.

 

 

그러나 “멀티 스레드”를 적용한 프로그램은

한 번에 하나의 작업을 수행하지 않을 수도 있다.

 

 

 

 

예를들어 메신저 프로그램을 예시로 들자면

우리는 채팅을 하면서 동시에 파일을 보낼 수 있다.

 

멀티쓰레드 프로그램은 “코드”, “스태틱” “힙”은 공유하지만

스택은 쓰레드 하나당 하나씩 생성된다.


 

 

멀티프로세스 vs 멀티쓰레드

프로세스 2개를 각각 워드와 엑셀로 실행했다고 가정한다.

독립된 프로세스 영역이기 때문에

워드가 오류가 나서 종류된다고 해도 엑셀이 영향을 받지는 않는다.

 

 

 

그러나 멀티쓰레드는 하나의 프로세스 내에서 여러개의 쓰레드가 돌아가기 때문에

프로세스 자체에 문제가 생기면 모든 쓰레드가 영향을 받는다.

 

그리고 이는 특정 쓰레드 하나에서 예외가 발생해 모든 프로세스가 먹통이 되는 상황도 포함한다.

 

 

 

 

쓰레드를 “경량 프로세스”라고 부르는데

멀티쓰레드 프로그램이 멀티프로세스에 비해 자원을 훨씬 덜 먹기 때문에

위와 같은 단점에도 불구하고 멀티쓰레드 프로그래밍이 중요하다고 하는 것이다.

 

즉, 멀티쓰레드 프로그램에서는 하나의 쓰레드에 문제가 생겨도 프로세스 자체에는

문제가 발생하지 않게 예외처리에 만전을 기해야 한다.

 


 

 

자바의 쓰레드

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

 

그리고 메인메서드는 코드를 순차적으로 위에서 아래로 실행하고

return구문이나 마지막 코드가 실행되고나면 실행이 종료됩니다.

 

메인쓰레드는 필요에 따라 보조 쓰레드를 추가로 실행할 수 있고

메인쓰레드가 호출한 보조 쓰레드들을 “작업쓰레드”라고 부른다.

 

싱글 쓰레드에서는 메인쓰레드가 종료되면 즉시 프로세스가 종료되었다.

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


 

 

작업 쓰레드 생성해보기

어떤 자바 어플리케이션이건 간에 우선 메인 메서드와 메인 쓰레드는 반드시 존재한다.

 

이외의 쓰레드에 대한 정의를 할 때는 클래스파일을 만들고자 하는 유형의 개수만큼 생성하면 된다.

 

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

 

먼저 아래와 같이 Thread 객체를 생성하고, Runnable을 파라미터로 갖는 생성자를 호출해야 한다.

 

Thread thread = new Thread(Runnable target);

 

해당 쓰레드가 실행할 내용은 아래와 같이 Runnable 인터페이스를 구현하고

 

run() 메서드를 작성하면 된다.

 

Class MultiThread implements Runnable{
	public void run(){
		// 해당 쓰레드가 실행할 코드들...
	}
}

 

위 로직에 따른 예시실행코드는 아래와 같다.

 

Runnable threadCode = new MultiThread(); // Runnable 구현체 생성

Thread thread = new Thread(threadCode); // 위에서 생성한 구현체를 매개변수로 넣어 생성자 호출

  • 만약 위처럼 Runnable을 따로 만들고 Thread객체를 따로 만드는게 불편하다면 아래와 같이
  • 익명객체를 생성해 사용한다.

 

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

 

어느 쪽을 선택하건 우선 Thread를 생성했다면 실행명령어는

thread.start(); 로 실행할 수 있다.(자동 생성 되지 않음!)

 

start() 가 호출되면 비로소 Runnable의 run() 메서드를 실행한다.

 

만약 특정 쓰레드의 실행결과를 기다려서 넘어가고 싶다면

중간에 thread.join()을 사용해 대기할 수 있다.

 

 

쓰레드의 우선순위

싱글코어 cpu를 이용한 멀티쓰레드는 실제로는 순차적으로 번갈아가면서 실행한다.

다만 속도가 워낙 빨라서 우리가 보기엔 그냥 동시에 작업이 이뤄지는것 처럼 보인다.

이를 동시성이라고 한다.

 

 

 

 

 

 

멀티코어 cpu를 이용한 멀티쓰레드가 진짜 동시에 여러 작업을 수행하는 것이며

 

 

이를 병렬성이라고 한다.

 

 

쓰레드의 공유객체 문제

기본적으로 쓰레드는 자신의 스택 영역 내부에서는 자신만 접근가능한 지역변수를 가지지만

힙이나 스태틱 영역은 공유하기 때문에 동시에 여러 쓰레드가 하나의 자원에 접근할 수도 있다.

 

아래 코드는 별다른 문제가 없어보이지만

2개의 쓰레드가 static 변수에 대해 동시에 접근해서

 

의도와 다른 결과가 나올 확률이 높아지는 문제를 가지고 있다.

 

package threadTest;

public class ThreadNotSafety extends Thread {
    
    static int share;
    
    public static void main(String[] args) {
      ThreadNotSafety t1 = new ThreadNotSafety();
      ThreadNotSafety t2 = new ThreadNotSafety();
      
      t1.start();
      t2.start();
    }
    
    public void run() {
      for(int count = 0; count < 10; count++){
        System.out.println(share++); 
        
        try { sleep(1000); }
        catch (InterruptedException e) {}
      }
    }
}

 

그림으로 그리면 아래와 같은 일들이 종종 발생한다.

 

 

 

그래서 특정 로직에 대해서는 동시에 하나의 쓰레드만 접근할 수 있도록 처리할 필요가 있다.

 

이때 사용하는 것이 synchronized 메서드다.

 

특정 메서드를 선언할때 synchronized 키워드를 붙이면

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

 

그 메서드는 동시에 하나의 쓰레드만 접근가능하며

 

다른 메서드들이 접근할 준비가 되어도 대기하기 때문에 공유객체문제를 해결할 수 있다.

 

또한 메서드 전체를 임계영역(단 하나의 쓰레드만 접근가능한 영역)으로 설정할 수도 있지만

 

특정 코드블럭만 임계영역으로 설정할수도 있다.

 

public void method() {
	
	// 실행코드

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

	// 실행코드
}