프로그램(program)이란?
프로그램(program)이란 실행 파일을 의미합니다.
프로세스(process)란?
프로세스(process)란 실행 중인 프로그램(program)이라고 할 수 있습니다.
사용자가 작성한 프로그램이 운영체제에 의해 메모리 공간을 할당받아 실행 중인 것을 말합니다.
프로세스는 프로그램에 사용되는 데이터, 메모리와 같은 자원 그리고 쓰레드로 구성됩니다.
쓰레드(thread)란?
쓰레드(thread)란 프로세스(process) 내에서 실제로 작업을 수행하는 작업 단위를 의미합니다.
모든 프로세스에는 한 개 이상의 쓰레드가 존재하여 작업을 수행합니다.
또한, 두 개 이상의 쓰레드를 가지는 프로세스를 멀티스레드 프로세스(multi-threaded process)라고 합니다.
1. 쓰레드의 생성과 실행
쓰레드를 생성하는 방법은 Runnable 인터페이스를 구현하는 법과 Thread 클래스를 상속 받는 법 두 가지가 있습니다.
두 방법 모두 스레드를 통해 작업하고 싶은 내용을 run() 메소드에 작성하면 됩니다.
1-1. Runnable 인터페이스를 구현하는 방법
package com.test01;
class MyThread01 implements Runnable{
@Override
public void run() {
for(int i = 0; i < 10; i++) {
System.out.println("i : " + i);
}
}
}
public class Thread01 {
// main thread이다. -> 우선순위가 높다.
public static void main(String[] args) {
System.out.println("main start ------------");
// Thread가 아님
/*
MyThread01 my01 = new MyThread01();
MyThread01 my02 = new MyThread01();
my01.run();
my02.run();
*/
// Thread임
Thread my01 = new Thread(new MyThread01());
Thread my02 = new Thread(new MyThread01());
my01.start();
my02.start();
System.out.println("main stop ------------");
}
}
실행 할 때 마다 결과가 다릅니다.
1-2. Thread 클래스를 상속받는 방법
package com.test01;
class MyThread02 extends Thread{
@Override
public void run() {
for(int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
public class Thread02 {
public static void main(String[] args) {
MyThread02 my01 = new MyThread02();
MyThread02 my02 = new MyThread02();
//start() -> run()을 자동 호출
my01.start();
my02.start();
}
}
마찬가지로 실행할때 마다 결과가 조금씩 다릅니다.
2. 쓰레드의 우선순위
각 쓰레드는 우선순위(priority)에 관한 자신만의 필드를 가지고 있습니다.
우선순위에 따라 특정 쓰레드가 더 많은 시간 동안 작업을 할 수 있도록 설정할 수 있습니다.
필드 | 설명 |
static int MAX_PRIORITY | 스레드가 가질 수 있는 최대 우선순위를 명시함. |
static int MIN_PRIORITY | 스레드가 가질 수 있는 최소 우선순위를 명시함. |
static int NORM_PRIORITY | 스레드가 생성될 때 가지는 기본 우선순위를 명시함. |
getPriority()와 setPriority() 메소드를 통해 쓰레드의 우선순위를 리턴하거나 변경할 수 있습니다.
쓰레드의 우선순위가 가질 수 있는 범위는 1부터 10까지이며 숫자가 높을수록 우선순위 또한 높아집니다.
하지만 우선순위가 10인 쓰레드가 우선순위가 1인 스레드보다 10배 더 빨리 수행되는 것이 아니며 우선순위는 비례적인 절댓값이 아니라 상대적인 값입니다.
단지 우선순위가 10인 쓰레드는 우선순위가 1인 스레드보다 좀 더 많이 실행 큐에 포함되어, 조금 더 많은 작업 시간을 할당받을 뿐입니다.
또한 쓰레드의 우선순위는 해당 쓰레드를 생성한 쓰레드의 우선순위를 상속받게 됩니다.
package com.test01;
class MyThread03 extends Thread {
public MyThread03(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(this.getName() + " : " + i);
}
System.out.println(this.getName() + " 끝 !!!!!!!!!!");
}
}
public class Thread03 {
public static void main(String[] args) {
MyThread03 dog = new MyThread03("멍멍");
MyThread03 cat = new MyThread03("냐옹");
// java의 thread scheduling은 우선순위(priority)와 순환할당(round-robin) 방식을 사용한다.
// priority : 우선순위
dog.setPriority(10);
cat.setPriority(Thread.MIN_PRIORITY);
dog.start();
cat.start();
}
}
package com.test01;
public class Thread04 {
public static void main(String[] args) {
MyThread03 m1 = new MyThread03("야옹");
MyThread03 m2 = new MyThread03("멍멍");
long startTime = System.currentTimeMillis();
m1.start();
try {
// join() : 해당 thread가 종료될 때 까지 다른 thread를 멈춤
m1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
m2.start();
long endTime = System.currentTimeMillis();
System.out.println("실행시간 : " + (endTime - startTime));
}
}
package com.test02;
class MyCalc extends Thread{
int start;
int end;
int sum;
public MyCalc(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public void run() {
sum = 0;
for (int i = start; i <= end; i++) {
sum += i;
System.out.println("Current Thread : " + currentThread().getName());
}
}
}
public class MTest01 {
public static void main(String[] args) {
MyCalc thread01 = new MyCalc(1, 5);
MyCalc thread02 = new MyCalc(6, 10);
thread01.start();
thread02.start();
try {
thread01.join();
thread02.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread01 sum : " + thread01.sum);
System.out.println("Thread02 sum : " + thread02.sum);
System.out.println("Total sum : " + thread01.sum + thread02.sum);
}
}
package com.test02;
public class MTest02 {
public static void main(String[] args) {
while(true) {
for(int i = 1; i <= 10; i++) {
try {
// Thread를 해당 millis 만큼 sleep ( 500m/s = 0.5초 )
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("%c ", '♥');
}
System.out.println();
}
}
}
하트가 10개가 되면 줄바꿈을 하고 0.5초마다 계속 생깁니다!
멀티 쓰레드(multi thread)
일반적으로 하나의 프로세스는 하나의 쓰레드를 가지고 작업을 수행합니다.
하지만 멀티 쓰레드란 하나의 프로세스가 둘 이상의 쓰레드가 동시에 작업을 하는 것을 의미합니다.
또한, 멀티 프로세스(multi process)는 여러 개의 CPU를 사용하여 여러 프로세스를 동시에 수행하는 것을 의미합니다.
멀티 쓰레드와 멀티 프로세스 모두 여러 흐름을 동시에 수행하다는 공통점을 가지고 있습니다.
멀티 프로세스는 각 프로세스가 독립적인 메모리를 가지고 별도로 실행되지만, 멀티 쓰레드는 각 쓰레드가 자신이 속한 프로세스의 메모리를 공유한다는 점이 다릅니다.
멀티 쓰레드는 각 쓰레드가 자신이 속한 프로세스의 메모리를 공유하므로, 시스템 자원의 낭비가 적습니다.
또한, 하나의 쓰레드가 작업을 할 때 다른 스레드가 별도의 작업을 할 수 있어 사용자와의 응답성도 좋아집니다.
문맥 교환(context switching)
컴퓨터에서 동시에 처리할 수 있는 최대 작업 수는 CPU의 코어(core) 수와 같습니다.
만약 CPU의 코어 수보다 더 많은 스레드가 실행되면, 각 코어가 정해진 시간 동안 여러 작업을 번갈아가며 수행하게 됩니다.
이때 각 쓰레드가 서로 교체될 때 쓰레드 간의 문맥 교환(context switching)이라는 것이 발생합니다.
문맥 교환이란 현재까지의 작업 상태나 다음 작업에 필요한 각종 데이터를 저장하고 읽어오는 작업을 가리킵니다.
이러한 문맥 교환에 걸리는 시간이 커지면 커질수록, 멀티 쓰레딩의 효율은 저하됩니다.
오히려 많은 양의 단순한 계산은 싱글 쓰레드로 동작하는 것이 더 효율적일 수 있습니다.
따라서 많은 수의 쓰레드를 실행하는 것이 언제나 좋은 성능을 보이는 것은 아니라는 점을 유의해야 합니다.
쓰레드 그룹(thread group)
쓰레드 그룹(thread group)이란 서로 관련이 있는 스레드를 하나의 그룹으로 묶어 다루기 위한 장치입니다.
자바에서는 쓰레드 그룹을 다루기 위해 ThreadGroup이라는 클래스를 제공합니다.
이러한 쓰레드 그룹은 다른 쓰레드 그룹을 포함할 수도 있으며, 이렇게 포함된 쓰레드 그룹은 트리 형태로 연결됩니다.
이때 스레드는 자신이 포함된 쓰레드 그룹이나 그 하위 그룹에는 접근할 수 있지만, 다른 그룹에는 접근할 수 없습니다.
이렇게 쓰레드 그룹은 쓰레드가 접근할 수 있는 범위를 제한하는 보안상으로도 중요한 역할을 하고 있습니다.
데몬 쓰레드(deamon thread)
데몬 쓰레드(deamon thread)란 다른 일반 쓰레드의 작업을 돕는 보조적인 역할을 하는 쓰레드를 가리킵니다.
따라서 데몬 스레드는 일반 쓰레드가 모두 종료되면 더는 할 일이 없으므로, 데몬 쓰레드 역시 자동으로 종료됩니다.
데몬 쓰레드의 생성 방법과 실행 방법은 모두 일반 쓰레드와 같습니다.
단, 실행하기 전에 setDaemon() 메소드를 호출하여 데몬 쓰레드로 설정하기만 하면 됩니다.
이러한 데몬 쓰레드는 일정 시간마다 자동으로 수행되는 저장 및 화면 갱신 등에 이용되고 있습니다.
가비지 컬렉터(gabage collector)
데몬 쓰레드를 이용하는 가장 대표적인 예로 가비지 컬렉터(gabage collector)를 들 수 있습니다.
가비지 컬렉터(gabage collector)란 프로그래머가 동적으로 할당한 메모리 중 더 이상 사용하지 않는 영역을 자동으로 찾아내어 해제해 주는 데몬 쓰레드입니다.
자바에서는 프로그래머가 메모리에 직접 접근하지 못하게 하는 대신에 가비지 컬렉터가 자동으로 메모리를 관리해 줍니다.
이러한 가비지 컬렉터를 이용하면 프로그래밍을 하기가 훨씬 쉬워지며, 메모리에 관련된 버그가 발생할 확률도 낮아집니다.
보통 가비지 컬렉터가 동작하는 동안에는 프로세서가 일시적으로 중지되므로, 필연적으로 성능의 저하가 발생합니다.
하지만 요즘에는 가비지 컬렉터의 성능이 많이 향상되어, 새롭게 만들어지는 대부분의 프로그래밍 언어에서 가비지 컬렉터를 제공하고 있습니다.
'Java 관련 > Java' 카테고리의 다른 글
[Java] 자바의 탄생과 특징 (0) | 2022.07.26 |
---|---|
[Java] 람다식(Lamda Expression) (0) | 2021.11.21 |
[Java] 입출력(IO - InputOutput) (0) | 2021.11.19 |
[Java] 예외 처리(Exception) (0) | 2021.11.18 |
[Java] Comparable & Comparator (0) | 2021.11.17 |