운영체제에 의해 메모리 공간을 할당받아 실행 중인 프로그램
프로세스는 프로그램에 사용되는 데이터와 메모리 등의 자원 그리고 스레드로 구성됨
프로세스 내에서 실제로 작업을 수행하는 주체를 의미합니다.
즉, 모든 프로세스에는 한 개 이상의 스레드가 존재하여 작업을 수행한다
스레드 생성하는 방법:
1. Runnable 인터페이스를 구현하는 방법
2. Thread 클래스를 상속받는 방법
두 방법 모두 스레드를 통해 작업하고 싶은 내용을 run() 메소드에 작성하면 된다
class ThreadWithClass extends Thread {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(getName()); // 현재 실행 중인 스레드의 이름을 반환함.
try {
Thread.sleep(10); // 0.01초간 스레드를 멈춤.
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class ThreadWithRunnable implements Runnable {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()); // 현재 실행 중인 스레드의 이름을 반환함
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Thread01 {
public static void main(String[] args){
ThreadWithClass thread1 = new ThreadWithClass(); // Thread 클래스를 상속받는 방법
Thread thread2 = new Thread(new ThreadWithRunnable()); // Runnable 인터페이스를 구현하는 방법
thread1.start(); // 스레드의 실행
thread2.start(); // 스레드의 실행
}
}
Thread-0
Thread-1
Thread-0
Thread-1
Thread-0
Thread-1
Thread-0
Thread-1
Thread-0
Thread-1
그러면 어떤 방법을 써야할까?
자바는 단일 상속만 되기 때문에 Thread 클래스를 상속받으면 다른 클래스를 상속받을 수 없으므로, 일반적으로 Runnable 인터페이스를 구현하는 방법으로 스레드를 생성한다
스레드의 우선순위
우선순위에 따라 특정 스레드가 더 많은 시간동안 작업할 수 있도록 설정하기 위해서 모든 스레드는 우선순위에 관한 필드를 가지고 있다
우선순위와 관련된 필드
static int MAX_PRIORITY
: 스레드가 가질 수 있는 최대 우선순위를 명시함
static int MIN_PRIORITY
: 스레드가 가질 수 있는 최소 우선순위를 명시함
static int NORM_PRIORITY
: 스레드가 생성될 때 가지는 기본 우선순위를 명시함
int형인것을 보니까 우선순위는 정수네!
=> 스레드의 우선순위가 가질 수 있는 범위는 1부터 10까지이며, 숫자가 높을수록 우선순위 또한 높아진다
getPriority()와 setPriority() 메소드를 통해 스레드의 우선순위를 반환하거나 변경할 수 있음
우선순위는 절대값이 아닌 상대값 이기 때문에 10인 스레드가 우선순위가 1인 스레드보다 10배 더 빨리 수행되는 것이 아니라 단순히 좀 더 많이 실행 큐에 포함되어 더 많은 작업 시간을 할당 받는 거임
추가로, 스레드의 우선순위는 해당 스레드를 생성한 스레드의 우선순위를 상속받게 된다
class ThreadWithRunnable implements Runnable {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()); // 현재 실행 중인 스레드의 이름을 반환함.
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Thread02 {
public static void main(String[] args){
Thread thread1 = new Thread(new ThreadWithRunnable());
Thread thread2 = new Thread(new ThreadWithRunnable());
thread2.setPriority(10); // Thread-1의 우선순위를 10으로 변경함.
thread1.start(); // Thread-0 실행
thread2.start(); // Thread-1 실행
System.out.println(thread1.getPriority());
System.out.println(thread2.getPriority());
}
}
5
10
Thread-1
Thread-0
Thread-1
Thread-0
Thread-1
Thread-0
Thread-1
Thread-0
Thread-1
Thread-0
main() 메소드를 실행하는 스레드의 우선순위는 언제나 5이므로, main() 메소드 내에서 생성된 스레드 Thread-0의 우선순위는 5로 설정됨
Thread-0을 먼저 실행하였지만, Thread-1의 우선순위가 더 높기 때문에 Thread-1이 먼저 실행된 것을 볼 수 있음
앞서 스레드를 설명할때 다음과 같이 설명했다
모든 프로세스에는 한 개 이상의 스레드가 존재하여 작업을 수행한다
한개 이상이라고 했으니 여러개도 가능하다는 소리네
멀티 스레드
하나의 프로세스 내에 두 개 이상의 스레드가 동시에 작업을 수행하는 것
하나의 스레드가 작업을 할 때 다른 스레드가 별도의 작업을 할 수 있으니까 사용자와의 응답성이 좋아진다
또 여러게 프로세스를 쓰는 것보다 하나를 쓰는게 자원을 공유하기 때문에 낭비가 적다
멀티 프로세스
여러 개의 CPU를 사용하여 여러 프로세스를 동시에 수행하는 것
멀티 스레드와 멀티 프로세스 모두 여러 흐름을 동시에 수행하다는 공통점을 가지고 있다
둘의 차이점은?
1. 멀티 프로세스는 각 프로세스가 독립적인 메모리를 가지고 별도로 실행되지만, 멀티 스레드는 각 스레드가 자신이 속한 프로세스의 메모리를 공유한다
2. 멀티 스레드는 각 스레드가 자신이 속한 프로세스의 메모리를 공유하므로, 시스템 자원의 낭비가 적다
문맥 교환(context switching)
컴퓨터에서 동시에 처리할 수 있는 최대 작업 수는 CPU의 코어 수와 같은데 CPU 코어 수보다 더 많은 스레드가 실행되면, 각 코어가 정해진 시간 동안 여러 작업을 번갈아가며 수행한다
그러면 스레드가 교체될텐데 현재까지의 작업상태를 저장하고, 다음 작업에 필요한 각종 데이터를 읽는 작업이 필요하다
문맥 교환:
문맥 교환이란 현재까지의 작업 상태나 다음 작업에 필요한 각종 데이터를 저장하고 읽어오는 작업을 가리킨다
이러한 문맥 교환에 걸리는 시간이 커지면 커질수록, 멀티 스레딩의 효율은 저하될 것이다
그래서 문맥교환이 너무 길면 오히려 싱글 스레드가 더 효율적일 수도 있음
(많은 수의 스레드를 실행하는 것이 언제나 좋은 성능을 보이는 것은 아님)
스레드 그룹
서로 관련이 있는 스레드를 하나의 그룹으로 묶어 다루기 위한 장치
자바에서는 스레드 그룹을 다루기 위해 ThreadGroup이라는 클래스를 제공한다
이러한 스레드 그룹은 다른 스레드 그룹을 포함할 수도 있으며, 이렇게 포함된 스레드 그룹은 트리 형태로 연결된다
이때 스레드는 자신이 포함된 스레드 그룹이나 그 하위 그룹에는 접근할 수 있지만, 다른 그룹에는 접근할 수 없다
이렇게 범위를 제한하면 보안상으로도 좋겠네
class ThreadWithRunnable implements Runnable {
public void run() {
try {
Thread.sleep(10); // 0.01초간 스레드를 멈춤.
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Thread03 {
public static void main(String[] args){
Thread thread0 = new Thread(new ThreadWithRunnable());
thread0.start(); // Thread-0 실행
System.out.println(thread0.getThreadGroup());
ThreadGroup group = new ThreadGroup("myThread"); // myThread라는 스레드 그룹 생성함.
group.setMaxPriority(7); // 해당 스레드 그룹의 최대 우선순위를 7로 설정함.
// 스레드를 생성할 때 포함될 스레드 그룹을 전달할 수 있음.
Thread thread1 = new Thread(group, new ThreadWithRunnable());
thread1.start(); // Thread-1 실행
System.out.println(thread1.getThreadGroup());
}
}
java.lang.ThreadGroup[name=main,maxpri=10]
java.lang.ThreadGroup[name=myThread,maxpri=7]
위의 예제처럼 main() 메소드에서 생성된 스레드의 기본 스레드 그룹의 이름은 "main"이 되며, 최대 우선순위는 10으로 자동 설정됨
데몬 스레드
다른 일반 스레드의 작업을 돕는 보조적인 역할을 하는 스레드
따라서 데몬 스레드는 일반 스레드가 모두 종료되면 더는 할 일이 없으므로, 데몬 스레드 역시 자동으로 종료된다
일반 스레드와 생성 및 실행 방법은 같고 실행 전에 setDaemon()메소드를 호출하여 데몬 스레드로 설정하기만 하면 된다
myThread.setDaemon(true);
주로 일정 시간마다 자동으로 수행되는 저장 및 화면 갱신 등 백그라운드 작업에 이용함
가비지 컬렉터
프로그래머가 동적으로 할당한 메모리(힙 영역) 중 더 이상 사용하지 않는 영역을 자동으로 찾아내어 해제해 주는 데몬 스레드
자바에서는 프로그래머가 메모리에 직접 접근하지 못하게 하는 대신에 가비지 컬렉터가 자동으로 메모리를 관리해 준다
이러한 가비지 컬렉터를 이용하면 프로그래밍을 하기가 훨씬 쉬워지며, 메모리에 관련된 버그가 발생할 확률도 낮아진다
거면 가비지 컬렉터는 장점만 있는가?
자동으로 처리해준다 해도 메모리가 언제 해제되는지 정확하게 알 수 없어 제어하기 힘들며 가비지 컬렉션(GC)이 동작하는 동안에는 다른 동작을 멈추기 때문에 오버헤드가 발생되는 문제점이 있다
이를 전문적인 용어로 Stop-The-World 라고 함
그러면 실시간 성이 매우 강조되는 포로그램일 경우 가비지컬렉터에게 메모리 관리를 맡기는게 안좋겠네
=> GC 최적화 작업(GC 튜닝)을 통해서 개발자가 성능을 높여야 한다
가비지 컬렉터에 관한 내용과 원리가 궁금해서 이는 검색을 해본 후 따로 정리해서 올릴 예정!