멀티 쓰레드의 실행은 동시성과 병렬성 중 하나로 실행된다. 동시성은 하나의 코어에서 여러 쓰레드가 실행되는 것으로 쓰레드를 번갈아가며 처리하는 것이고 병렬성은 멀티 코어를 사용해 코어별로 쓰레드가 실행되어 쓰레드들을 동시에 처리하는 것이다.
쓰레드를 구현하는 방법은 Runnable 인터페이스를 사용, Thread클래스를 사용 으로 두가지이다. Runnable과 Thread모두 java.lang 패키지에 포함되어 있고 Thread클래스는 Runnable 인터페이스를 구현한 클래스이다.
public class ThreadTest extends Thread{
//run 메소드를 오버라이딩하여 사용
public void run(){
}
}
public class Thread1{
public static void main(String args[]){
ThreadTest t1 = new ThreadTest();
t1.start();
}
}
public class RunnableTest implements Runnable{
public void run(){
}
}
public class Thread2{
public static void main(string args[]){
Thread t2 = new Thread(new RunnableTest());
t2.start();
}
}
이 두가지 방법 모두 Thread의 start()를 통해 실행하게 되는데 Thread의 경우 해당 객체의 start()를 직접 호출할 수 있지만 Runnable을 구현한 클래스의 경우에는 Runnable형 인자를 받는 생성자를 통해 별도의 Thread 객체를 생성한 후 start() 메소드를 호출해야 한다.
Thread 클래스를 확장하는 것이 실행 방법이 더 간단하지만 다중 상속을 허용하지 않는 자바의 특성상 Thread클래스를 확장하게 되면 다른 클래스는 상속받을 수 없게 된다.
하지만 Runnable 인터페이스를 구현했을 경우는 다른 클래스를 상속받을 수 있기에 Runnable클래스를 구현하는 것을 더 선호하고 있다.
쓰레드는 start() 메소드를 호출하는 순간 실행된다. start()를 호출하는 순간 해당 쓰레드에서 사용할 호출 스택이 생성된다. 이 쓰레드는 대기상태가 되며 실제 실행될때 Thread에 run()이 수행되게 된다. 하지만 run()메소드를 호출하는 것은 생성된 쓰레드를 실행시키는 것이 아닌 단순히 메소드를 호출하는 것이다.


NEW : 쓰레드가 생성되고 아직 start()가 호출되지 않는 상태RUNNABLE : 실행 중 또는 실행 가능한 상태BLOCKED : 동기화블럭에 의해서 일시정지된 상태 (lock이 풀릴 때까지 정지)WAITING, TIMED_WAITING : 작업이 실행가능하지 않은 일시정지 상태, TIMED_WAITING은 일시정지시간이 지정된 경우를 의미한다.TERMINATED : 쓰레드의 작업이 종료된 상태
쓰레드의 생서부터 소멸까지
1. 쓰레드를 생성한고 start()를 호출해 실행 대기열에 저장되어 자신의 차례가 될 때까지 기다린다.
2. 자기 차례가 되면 실행상태가 된다.
3. 할당된 실행시간이 다되거나 yield()메소드를 만나면 다시 실행 대기상태가 되고 다음 쓰레드가 실행상태가 된다.
4. 실행 중에 suspend(), wait(), join(), I/O block에 의해 일시정지상태가 될 수 있다. (I/O block : 입출력 작업에서 발생하는 지연상태)
5. 지정된 일시정지시간이 다 되거나 notify(), resume(), interrupt()가 호출되면 다시 실행 대기열에 저장되어 자신의 차례를 기다린다.
6. 실행을 모두 마치거나 stop()이 호출되념 쓰레드는 소멸된다.
쓰레드는 우선순위(priority)라는 속성(멤버변수)을 가지고 있는데 이 우선순위의 값에 따라 쓰레드가 얻는 실행시간이 달라진다. 이를 활용하여 특정 쓰레드가 더 많은 작업시간을 가지도록 할 수 있다.
public final static int MIN_PRIORITY = 1 : 쓰레드가 가질 수 있는 우선 순위의 최소값public final static int NORM_PRIORITY = 5 : 쓰레드가 가지는 우선 순위의 기본값public final static int MAX_PRIORITY = 10 : 쓰레드가 가질 수 있는 우선 순위의 최대값void setPriority(int priority), int getPriority()를 사용하여 스레드의 우선 순위를 변경할 수 있다.public class ThreadExample{
public static void main(String[] args){
MyThread_1 t1 = new MyThread_1();
MyThread_2 t2 = new MyThread_2();
t1.setPriority(1);
t2.setPriority(8);
}
}
Java의 실행환경인 JVM은 하나의 프로세스로 실행된다. 이 말은 자바 애플리케이션이 기본적으로 하나의 메인 쓰레드를 가진다는 말이다.
public static void main(String[] args){ //메인 쓰레드의 시작
} //메인 쓰레드의 끝
Main 쓰레드는 자바에서 처음으로 실행되는 쓰레드이고 다른 쓰레드는 이 Main 쓰레드로부터 생성된다. Main 쓰레드가 종료되더라도 생성된 쓰레드가 실행 중이라면 모든 쓰레드의 작업이 완료되어 종료될때까지 프로그램은 종료되지 않는다.
싱글쓰레드 프로세스의 경우에는 프로세스 내에서 단 하나의 쓰레드만 작업을 하기에 프로세스의 자원을 사용하는 것에 문제가 없다. 하지만 멀티 쓰레드의 경우 여러 쓰레드가 같은 프로세스 내의 자원을 공유해서 작업하기 때문에 서로 영향을 주게 된다.
쓰레드의 동기화(synchronization)는 한 쓰레드가 진행 중인 작업을 다른 쓰레드가 간섭하지 못하도록 막는 것을 말한다. 기존에는 synchronized블록을 이용해 동기화를 하였지만 JDK1.5부터는 java.util.concurrent.locks와 java.util.concurrent.atomic 패키지등으로 다양하게 동기화를 구현할 수 있다.
임계구역 : 공유자원 접근 순서에 따라 실행결과가 달라지는 프로그램의 영역
자바에서는 synchronized를 통해 공유데이터를 사용하는 임계구역의 공유데이터가 가지고있는 lock을 획득한 단 하나의 쓰레드만 이 구역의 코드를 수행할 수 있게 한다.
synchronized(객체의 참조변수){
...
}
lock을 걸고자하는 객체를 지정해주며 이 블록의 영역 안으로 들어가면 쓰레드는 지정된 객체의 lock을 얻게 되고 이 블록을 벗어나면 lock을 반납한다.
public synchronized void test(){
...
}
메소드가 호출된 시점부터 lock을 얻어서 작업한 후 메소드가 종료될 때 lock을 반환한다.
💡 임계구역은 멀티쓰레드 프로그램의 성능을 좌우하기 때문에 가능한 synchronized블록으로 임계구역을 최소화하는 것이 중요한다.
데드락은 교착상태로 2개 이상의 프로세스가 다른 프로세스의 작업이 끝나기를 기다리다 작업을 더 이상 진행하지 못하는 상태를 말한다.
교착상태가 발생하기 위해서는 다음의 네가지 조건이 동시에 성립해야 한다.
Reference
본 스터디는 2020 백기선님의 자바스터디의 커리큘럼을 참고하여 진행하고 있습니다.