본문 바로가기

Programming Language/JAVA

[JAVA] 쓰레드(thread)와 프로세스(process),멀티쓰레드, 멀티프로세싱, 데몬쓰레드(Daemon),동기화(Synchronized)

 

▶ [JAVA] 쓰레드(thread)와 프로세스(process),멀티쓰레드, 멀티프로세싱, 데몬쓰레드(Daemon),동기화(Synchronized) 

 

 

 

프로세스(Process)
: 컴퓨터에서 연속적으로 실행되고 있는 컴퓨터 프로그램입니다. 또한, 메모리에 올라와 실행되고 있는 프로그램의 독립적인 개체입 니다. Code, Data, Stack, Heap영역의 구조로 되어있는 독립된 메모리 영역입니다.

 

 

프로세스(Process) 특징
- 프로세스는 각각 독립된 메모리영역을 할당받는다.
- 기본적으로 프로세스당 최소 1개의 스레드(메인스레드)가 존재한다.
- 각 프로세스는 별도의 주소 공간에서 실행되며, 한 프로세스는 다른 프로세스의 변수나 자료구조에 접근이 불가능하다.
- 한 프로세스가 다른 프로세스의 자원에 접근하려하면 프로세스 간의 통신(IPC, inter-process Communication)을 사용해야 한다.



쓰레드(Thread)
: 프로세스 내에서 실행되는 여러 흐름의 단위이며, 프로세스의 특정한 수행경로입니다.

쓰레드(Thread) 특징
- 쓰레드는 프로세스 내에서 각각 stack만 따로 할당받고 Code,Data,Heap영역은 공유한다.
- 쓰레드는 한 프로세스 내에서 동작되는 여러 실행의 흐름으로, 프로세스 내에 주소 공간이나 자원들(힙 공간 등)을 같은
프로세스내에 쓰레드끼리 공유하면서 실행된다.
- 같은 프로세스 안에 있는 여러 쓰레드들은 같은 힙 공간을 공유한다. 반면에 프로세스는 다른 프로세스의메모리에 직접
접근이 불가능하다.
- 각각의 쓰레드는 별도의 레지스터와 스택을 갖고있지만, 힙 메모리는 서로 읽고 쓸수있다.

 



멀티 프로세싱
: 하나의 응용프로그램을 여러 개의 프로세스로 구성하여 각 프로세스가 하나의 작업을 처리하게한다.

멀티 쓰레드

: 하나의 응용프로그램을 여러개의 쓰레드로 구성하고 각 쓰레드로 하여금 한하나의 작업을 처리하게한다.
대표적인 멀티쓰레드 응요프로그램은 웹서버이다.




멀티 프로세싱으로 할수 있는 작업들을 멀티 쓰레드로 사용하는 이유
1. 자원의 효율성 증대
멀티 프로세스로 실행되는 작업을 멀티 스레드로 실행할 경우, 프로세스를 생성하여 자원을 할당하는 시스템 콜이 줄어들어 자 원을 효율적으로 관리할 수 있다.
2. 처리 비용 감소 및 응답 시간 단축
또한 프로세스 간의 통신(IPC)보다 스레드 간의 통신의 비용이 적으므로 작업들 간의 통신의 부담이 줄어든다.

 

 

 

class A{
	public void run() {
		for(int i=0;i<100;i++) {
			System.out.println("A: "+i);
		}
	}
}
class B{
	public void run() {
		for(int i=0;i<100;i++) {
			System.out.println("B: "+i);
		}
	}
}

public class Test {
	public static void main(String[] args) {
		A a = new A();
		B b = new B();
		a.run();
		b.run();
	}

}

쓰레드를 사용하기전 소스를 만들어봤습니다.
위소스를 실행하면 A클래스의 run()메소드가 먼저 100번까지 출력되고,
B클래스의 run() 메소드가 100번까지 출력됩니다. 현재 Main쓰레드 하나만을 돌렸기때문에 즉, 쓰레드 생성없이했기에
순차적으로 출력되는걸 확인할수있습니다.

 

 

 

 

class A extends Thread{
        @Override
	public void run() {
		for(int i=0;i<100;i++) {
			System.out.println("A: "+i);
		}
	}
}
class B extends Thread{
        @Override
	public void run() {
		for(int i=0;i<100;i++) {
			System.out.println("B: "+i);
		}
	}
}

public class Test {
	public static void main(String[] args) {
		A a = new A();
		B b = new B();
		a.start();
		b.start();

		System.out.println("프로그램종료");
		System.out.println("프로그램종료");
		System.out.println("프로그램종료");
	}

}

이제 위와 같이 A클래스와 B클래스에 Thread를 상속받았습니다.
쓰레드를 사용하기위해서 Thread클래스를 상속받아 쓰기도하는데요.
처음 코드와 바뀐것은 extends키워드를 사용해서 상속을 받았고, Main메소드에서 run()메소드 호출이아닌 start() 메소드를 호출했습니다.

즉, Thread클래스의 run메소드를 오버라이딩했고, run메소드를 호출하려면 start메소드로 호출하도록 JDK에서 만들어놓았습니다. 실행결과는 기존에 Main쓰레드가 다른 쓰레드들에게 일을 맡기고 먼저 프로그램 종료의 문구를 출력합
니다. 그리고나서 각각의 쓰레드들이 숫자를 출력해주는데 console창을 보면 랜덤으로 A,B클래스의 숫자가 출력되는걸 확인할수있습니다.

 

 

 

 

package Test;

class A extends Thread{
	@Override
	public void run() {
		for(int i=0;i<100;i++) {
			System.out.println("A: "+i);
		}
	}
}
class B extends Thread{
	@Override
	public void run() {
		for(int i=0;i<100;i++) {
			System.out.println("B: "+i);
		}
	}
}

public class Test {
	public static void main(String[] args) {
		A a = new A();
		B b = new B();
		a.start();
		b.start();
		
		try {
			Thread.sleep(5000);
		}catch (Exception e) {
			// TODO: handle exception
		}
		
		
		System.out.println("프로그램종료");
		System.out.println("프로그램종료");
		System.out.println("프로그램종료");
	}

}

위에서는 sleep이라는 키워드를 사용해봤습니다.
Main쓰레드가 각각의 쓰레드들에게 일을 실행하게 하고 프로그램종료 문구출력전에 sleep메소드를 호출해서 5초뒤에
출력되는걸 확인할수있습니다. 그러면 다른 쓰레드들보다 늦게 프로그램 종료문구가 늦게출력됩니다.

 

 

 

 


 

 

 

데몬쓰레드
: 다른 일반 쓰레드의 작업을 돕는 보조적인 역할을 수행하는 쓰레드이며, 일반 쓰레드가 모두 종료되면 데몬 쓰레드는 강제적으로 자동종료된다.

 

 

 

 

 

package Test;

import java.util.Scanner;

class A extends Thread{
	@Override
	public void run() {
		for(;;) {
			try {
				Thread.sleep(1000);
				System.out.println("쓰레드가동 ");
			}catch (Exception e) {
			}
		}
	}
}

public class Test {
	public static void main(String[] args) {
		Scanner input = new Scanner(System.in);
		String str;
		A a = new A();
		a.start();
		try {
			str=input.nextLine();
			System.out.println("입력: "+str);
			throw new Exception("메인 종료");
		}catch (Exception e) {
			System.out.println("프로그램 자동 저장 완료");
		}
		
		System.out.println("프로그램종료");
		System.out.println("프로그램종료");
		System.out.println("프로그램종료");
	}

}

이번에는 데몬쓰레드에 대해 알아보겠습니다.
위 소스를 입력하면 사용자가 어떤한 값이든 console창을 이용해서 입력해도 "쓰레드가동"이라는 문구가 계속 출력됩니다.
즉, Main쓰레드가 종료되도, 일을하고있는 쓰레드는 계속적으로 구동되고있는것입니다.

 

 

 

 

package Test;

import java.util.Scanner;

class A extends Thread{
	@Override
	public void run() {
		for(;;) {
			try {
				Thread.sleep(1000);
				System.out.println("쓰레드가동 ");
			}catch (Exception e) {
			}
		}
	}
}

public class Test {
	public static void main(String[] args) {
		Scanner input = new Scanner(System.in);
		String str;
		A a = new A();
		a.setDaemon(true);
		a.start();
		try {
			str=input.nextLine();
			System.out.println("입력: "+str);
			throw new Exception("메인 종료");
		}catch (Exception e) {
			System.out.println("프로그램 자동 저장 완료");
		}
		
		System.out.println("프로그램종료");
		System.out.println("프로그램종료");
		System.out.println("프로그램종료");
	}

}

위에서는 a.setDaemon(true);라는 문구를 사용해서 Main쓰레드가 종료되면 a객체의 쓰레드도 종료시키라는 의미입니다.
실행해보면 사용자가 문장입력한후, 문자 출력되고 프로그램이 종료됨과 동시에 "쓰레드가동"이라는 문구도 종료됩니다.
Main쓰레드가 종료되었기에, 다른쓰레드(a객체의 쓰레드)도 종료되게 하는것이 데몬쓰레드의 사용입니다.

 

 

 


 

 

 

동기화(Synchronized)
: 먼저 접근한 쓰레드가 작업을 마칠 때까지 나머지 쓰레드는 작업을 수행하지 않고 기다리게 하는 역할입니다.

 

 

 

package Test;
class 가족 extends Thread{
	private String 누구;
	private 화장실 wr;
	가족(String name, 화장실 wr){
		누구 = name;
		this.wr=wr;
	}
	public void run() {
		wr.openDoor(누구);	
	}
}

class 화장실{
	public void openDoor(String name) {
		System.out.println(name+"님이 사용합니다.");
		try {
			Thread.sleep(5000);
		}catch(Exception e) {}
		System.out.println(name+"님이 사용을 마치셨습니다.");
	}
}
public class Test {

	public static void main(String[] args) {
		화장실 wr;
		wr = new 화장실();
		가족 아빠 = new 가족("아빠",wr);
		가족 엄마 = new 가족("엄마",wr);
		가족 누나 = new 가족("누나",wr);
		가족 동생 = new 가족("동생",wr);
		가족 나 = new 가족("나",wr);
		
		아빠.start();
		엄마.start();
		누나.start();
		동생.start();
		나.start();
	}

}

먼저 동기화를 사용하기전 소스입니다.

소스를 실행해보면 Main쓰레드가 각각의 가족들의 쓰레드를 실행해서 가족들 모두 한번에 사용합니다가 뜨고
5초후에 사용을 마쳤다는 문구가 출력됩니다. 하지만, 화장실은 하나인데 모두들어갈수없듯이 사용한사람이 마치고나오면 그다음사람이 들어가게하기위해 동기화라는 것이 존재합니다.

 

 

 

 

 

package Test;
class 가족 extends Thread{
	private String 누구;
	private 화장실 wr;
	가족(String name, 화장실 wr){
		누구 = name;
		this.wr=wr;
	}
	public void run() {
		wr.openDoor(누구);	
	}
}

class 화장실{
	public synchronized void openDoor(String name) {
		System.out.println(name+"님이 사용합니다.");
		try {
			Thread.sleep(5000);
		}catch(Exception e) {}
		System.out.println(name+"님이 사용을 마치셨습니다.");
	}
}
public class Test {

	public static void main(String[] args) {
		화장실 wr;
		wr = new 화장실();
		가족 아빠 = new 가족("아빠",wr);
		가족 엄마 = new 가족("엄마",wr);
		가족 누나 = new 가족("누나",wr);
		가족 동생 = new 가족("동생",wr);
		가족 나 = new 가족("나",wr);
		
		아빠.start();
		엄마.start();
		누나.start();
		동생.start();
		나.start();
	}

}

위소스에서 화장실class에서 operDoor메소드를 보면 synchronized라는 키워드를 사용해서 화장실 문을 들어가는 메소드에서 쓰레드가 사용중이면 마칠때까지 기다렸다가 다음 순서가 들어가고 마치고 또 다음순서가 들어가도록 출력했습니다.
이처럼 쓰레드가 실행중이면 그 다음사람은 그걸 기다렸다가 종료되면 다음쓰레드가 실행되게 하는것입니다.