프로세스란 실행중인 프로그램 을 의미한다.
프로세스는 프로그램을 수행하는 데 필요한 데이터와 자원 그리고 쓰레드로 구성되어 있으며, 실제로 작업을 수행하는 것이 바로 쓰레드이다.
모든 프로세스에는 최소한 하나 이상의 쓰레드가 존재하며, 둘 이상의 쓰레드를 가진 프로세스를 멀티쓰레드 프로세스 라고 한다.
쓰레드는 프로세스라는 작업 공간에서 일하는 일꾼이라고 생각하면 좋다.
멀티태스킹 : 여러 개의 프로세스가 동시에 실행되는 것
멀티쓰레딩 : 하나의 프로세스에서 여러 개의 쓰레드가 동시에 작업하는 것.
CPU 코어는 한 번에 하나의 작업만 수행할 수 있다. -> 작업의 개수 = 코어의 개수
쓰레드의 수는 언제나 코어의 개수보다 많다. -> 각각의 코어가 아주 짧은 시간동안 여러 작업을 번갈아가며 수행한다. 이는 매우 짧은 시간 사이에 일어나므로, 작업이 동시에 수행되는것 처럼 보인다.
장점
단점
동기화 문제
교착상태 문제
교착상태 : 두 쓰레드가 자원을 점유한 상태에서 서로 상대방의 자원을 사용하려고 기다리느라 진행이 멈춘 상태.
쓰레드의 구현은 두 가지 방법이 있다.
Thread 클래스 상속
public class Main {
public static void main(String[] args) {
Thread1 t1 = new Thread1();
t1.start();
}
}
class Thread1 extends Thread{
public void run(){
for(int i=0; i<5; i++){
System.out.println(getName());
}
}
}
/*
출력 결과
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
*/
Thread 클래스를 상속받은 경우에는 객체를 생성할 때, 자손 클래스의 객체를 생성한다.
그리고 getName() 메서드를 호출해 이름을 반환할 수 있다.
Runnable 인터페이스를 구현
Runnable 인터페이스란 run() 메서드만 정의된 간단한 인터페이스이다.
public class Main {
public static void main(String[] args) {
Runnable r = new Thread2();
Thread t2 = new Thread(r);
t2.start();
}
}
class Thread2 implements Runnable{
public void run(){
for (int i=0; i<5; i++){
System.out.println(Thread.currentThread().getName());
}
}
}
/*
출력 결과
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
*/
Runnable 인터페이스를 구현한 경우에는 객체를 생성할 때, 구현 클래스의 객체를 생성한 다음, 이 객체를 Thread 클래스의 생성자의 매개변수로 제공한다.
Runnable 인터페이스의 구현 클래스 멤버는 run() 메서드 밖에 없기 때문에 getName()을 호출하려면 Thread.currentThread().getName()과 같이 써야한다.
아래의 두 가지 이유로 인해 일반적으로
Runnable인터페이스를 구현하는 방식을 사용한다.
Thread클래스를 상속받는 방식을 사용하면 다른 클래스를 상속받을 수 없다.객체지향적인 관점에서
Runnable인터페이스를 구현하는 방법이 재사용성이 높고 코드의 일관성을 유지할 수 있다.
위의 코드에서 쓰레드를 실행시킬 때, run() 을 직접 호출하지 않고, start() 를 호출한다.
run() 을 호출하는 것은 쓰레드의 실행이 아닌, 단순히 선언된 메서드를 호출하는 것일 뿐이라는 점을 기억하자.
start() 메서드는 새로운 쓰레드가 작업을 실행하는데 필요한 공간(호출스택) 을 생성한 후에 run()을 호출해서, 생성된 호출스택에 run()이 첫 번째로 올라가게 한다.
한 번 실행이 종료된 쓰레드는 다시 실행할 수 없다. 즉, 하나의 쓰레드는
start()가 한 번만 호출될 수 있다.Thread1 t1 = new Thread1(); t1.start(); // t1.start(); 한 번 더 호출시 IlleagalThreadStateException 발생 // 따라서 다시 수행해야 한다면 아래와 같이 새로운 쓰레드 생성 t1 = new Thread1(); t1.start();
그렇다면 위의 Thread1 클래스와 Thread2 클래스를 멀티 쓰레드로 처리해보자.
반복문이 너무 짧으면 멀티쓰레드를 확인할 수 없으므로 300번씩 반복해서 "0"과 "1"를 출력하게 만들었다.
public class Main {
public static void main(String[] args) {
Thread1 t1 = new Thread1();
Runnable r = new Thread2();
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
}
class Thread1 extends Thread{
public void run(){
for(int i=0; i<300; i++){
// 줄바꿈을 없애기 위해 printf 사용
System.out.printf("%s", new String("0"));
}
}
}
class Thread2 implements Runnable{
public void run(){
for (int i=0; i<300; i++){
System.out.printf("%s", new String("1"));
}
}
}
/*
출력 결과
000000000000000000001111111111111111111111111111111111111111111111111111111111111111101111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
*/
위의 출력 결과와 같이 순차적으로 실행되는 것이 아닌, 동시에 실행 되는 것을 확인할 수 있다.
싱글쓰레드와 멀티쓰레드의 사용은 상황에 따라 효율이 다르니 잘 고려해서 사용하는 것이 좋다.