프로세스와 스레드의 차이
프로세스(공장) : 실행중인 프로그램이란 뜻이다. OS로 부터 실행에 필요한 자원을 할당받아 프로세스가 된다. 모든 프로세스는 하나 이상의 스레드를 가진다.
스레드(일꾼) : 프로세스 내부에서 작업을 수행하는 것이다. 호출스택만 있다면 무한정 만들어 낼 수 있다.
멀티 태스킹 : 여러개의 프로세스를 동시에 수행한다. cpu의 코어의 개수와 일치한다.
멀티 스레딩 : 하나의 프로세스에 여러개의 스레드를 수행한다.
*스레드를 생성하는 것보다 프로세스를 생성하는데 더 자원(시간,공간)이 필요하다.
*멀티 스레드는 하나의 프로세스(할당된 공간)에서 자원을 같이 사용하기 때문에 동기화,교착상태와 같은 문제들이 발생할 수 있다.
쓰레드의 구현과 실행
- Thread 클래스 상속 (다른 클래스 상속 불가능)
- Runnable 인터페이스 구현 (다른 클래스 상속 가능해서 주로 사용됨)
class ThreadEx1{
public static void main(String args[]){
ThreadEx1_1 t1 = new ThreadEx1_1();
t1.start();
Runnable r = new ThreadEx1_2();
Thread t2 = new Thread(r);
t2.start();
}
}
class ThreadEx1_1 extends Thread{
pubilc void run(){
/* 구현부 */
}
}
class ThreadEx1_2 implements Runnable{
pubilc void run(){
/* 구현부 */
}
}
start() 와 run()의 차이(호출스택 관련)
Tread를 구현하게 되면 run()함수에 구현부를 작성해야한다. 즉, Thread 인스턴스에서 run()함수를 곧바로 불러서 사용할 수 있다는 이야기다. 그런데 우리는 start()라는 함수를 사용한다.
이유는 아래와 같다. start()함수를 사용하게 되면 새로운 호출 스택을 생성하고 그위에 run()함수를 실행한다. 이후 start()함수가 종료되고 main()함수가 종료되면 run()일 돌아가는 Call stack만 남겨지게 된다. 그러면 process에서는 하나이상의 호출 스택이 돌아가고 있기 때문에 종료되지 않는다. 이후 run()이 종료되면 프로세스가 종료된다.
*(프로세스에서 실행중인 사용자 스레드가 없으면 프로세스가 종료된다.)
만약 run()함수를 직접 호출하는 경우 main()이 실행되고 있는 호출스택에 run()함수가 실행된다. 그럼 run()이 종료되면 main()함수가 차례로 종료되고 프로세스가 종료된다.
스레드 그룹
스레드 그룹을 생성하면 그룹으로 스레드를 묶어서 관리할 수 있다. 원래는 보안상의 이유로 도입된 개졈으로, 자신이 속한 스레드 그룹이나 하위 스레드 그룹은 변경할 수 있지만 다른 다른 스레드 그룹의 스레드를 변경할 수 없다. 만약 스레드 그룹을 따로 지정하지 않는다면 자신을 생성한 스레드 그룹에 속하게 된다.
JVM은 main과 system스레드 그룹을 생성하고 main에는 main메소드를 실행하는 스레드가 생성되고, system스레드에는 가비지컬렉션을 수행하는 Finalizer 스레드가 생성된다.
사용자가 생성하는 모든 스레드 그룹은 main스레드 그룹의 하위 스레드 그룹이 되고 스레드 그룹을 지정하지 않으면 자동적으로 main스레드 그룹에 속하게 된다.
public class ThreadEx9 {
public static void main(String args[]) throws Exception{
ThreadGroup main = Thread.currentThread().getThreadGroup(); // 기본 스레드가 main스레드 그룹이다.
ThreadGroup grp1 = new ThreadGroup("Group1"); // 스레드 그룹을 생성한다.
ThreadGroup grp2 = new ThreadGroup("Group2");
// ThreadGroup
ThreadGroup subGrp1 = new ThreadGroup(grp1, "subGroup1"); // grp1안에 하위 스레드 그룹을 생성
grp1.setMaxPriority(3); // 스레드 그룹 grp1의 최대 우선순위를 3으로 변경 => 기본 priority가 5이기 때문에 우선순위 낮아짐.
Runnable r = new Runnable() {
@Override
public void run() {
try{
Thread.sleep(5000);
System.out.print(Thread.currentThread().getName()+"\\n");
}catch (InterruptedException e){}
}
};
new Thread(grp1, r, "th1").start(); // 스레드 생성과 동시에 실행, grp1은 스레드의 우선순위가 밀리기 때문에 나중에 실행됨
new Thread(subGrp1, r, "th2").start();
new Thread(grp2, r, "th3").start();
System.out.println((">>LIST of ThreadGroup: "+ main.getName()
+", Active ThreadGroup: "+ main.activeGroupCount()
+", Active Thread: "+ main.activeCount())); // 바로 실행됨
}
}
데몬 스레드
다른 일반 스레드의 작업을 돕는 보조적인 역할을 수행하는 스레드이다. 즉, 일반 스레드가 종료되면 데몬 스레드의 존재의 의미가 없어져서 강제 종료된다.(ex. 가비지 컬렉터, 워드프로세서의 자동저장, 화면 자동 갱신)
데몬 스레드는 무한루프와 조건문을 이용해서 실행 후 대기하고 있다가 특정 조건이 만족되면 작업을 수행하고 다시 대기하도록 작성한다. 데몬 스레드는 일반 스레드의 작성방법과 같고, 다만 스레드를 생사한 다음 실행하기 전에 setDaemon(true)를 호출하면 된다.
public class ThreadEx10 implements Runnable{
static boolean autoSave = false; // 메모리를 공유가능해서 여러 객체에도 하나만 참조한다.
public static void main(String args[]){
Thread t = new Thread(new ThreadEx10());
t.setDaemon(true);
t.start();
for(int i=1; i<10; i++){
try{
Thread.sleep(1000);
}catch (InterruptedException e){}
System.out.println(i);
if(i == 5){
autoSave = true;
}
}
System.out.println("프로그램을 종료합니다.");
}
@Override
public void run() {
while(true){
try{
Thread.sleep(3000);
}catch (InterruptedException e){}
if (autoSave){
autoSave();
}
}
}
public void autoSave(){
System.out.println("작업파일이 자동 저장되었습니다.");
}
}
스레드의 실행제어
스레드 프로그래밍이 어려운 이유는 동기화와 스케줄링때문이다. 효율적인 스레드를 위해서는 스케줄링을 잘해야하는데 스레드의 상태와 관련 메소드를 잘 알아야한다.
static void sleep(long millis)
static void sleep(long millis, int nanos)
⇒ 지정된 시간동안 스레드를 일시정지시키고, 지정 시간이 지나면 자동적으로 다시 실행대기상태가 된다.
(*sleep은 항상 현재 실행중인 쓰레드에 대해 동작하기 때문에 th1.sleep(2000)으로 호출되어도 main메서드를 실행하는 main스레드가 영향을 받는다.)
void join()
void join(long millis)
void join(long millis, int nanos)
⇒ 지정된 시간동안 스레드가 실행되도록한다. 지정된 시간이 지나거나 작업이 종료되면 join을 호출한 스레드로 다시 돌아와 실행을 계속한다.
void interrupt()
⇒ sleep, join에 의해 일시정지상태인 쓰레드를 깨워서 실행대기상태로 만든다. 해당 스레드는 InterruptException이 발생하여 일시정지 상태를 벗어난다.
void stop()
⇒ 스레드를 즉시 종료시킨다.
void suspend()
⇒ 스레드를 일시정지시킨다. resume()을 호출하면 다시 실행대기상태가 된다.
void resume()
⇒ suspend()에 의해 일시정지상태에 있는 스레드를 실행대기상태로 만든다.
static void yield()
⇒ 실행 중에 자신에게 주어진 실행시간을 다른 쓰레드에게 양보하고 자신은 실행대기상태가 된다.
스레드의 동기화(임계영역, lock)
멀티 스레드 프로세스의 경우 여러 쓰레드가 같은 프로세스 내의 작업을 공유해서 작업하기 때문에 서로의 작업에 영향을 주게된다. 그런데 서로 작업을 번갈아 하는 경우 의도와 다르게 공유데이터를 훼손 시킬 수 있다. 따라서 이러한 일을 방지하기 위해 한 스레드가 특정 작업을 끝마치기 전까지 다른 스레드에 의해 방해받지 않게 해야한다. 이게 바로 임계영역(critical section)과 잠금(lock)이다. 공유 데이터를 사용하는 코드 영역을 임계영역으로 지정하고 공유 데이터가 가지고 있는 lock을 획득한 단 하나의 스레드만 이 영역 내의 코드를 수행할 수 있게 한다. 해당 임계영역내의 모든 코드를 수행하고 벗어나서 lock을 반납해야만 다른 스레드가 반납된 lock을 획득하여 임계 영역의 코드를 수행할 수 있게 된다.
⇒ 한 스레드가 진행 중인 작업을 다른 스레드가 간섭하지 못하도록 하는 것을 “스레드 동기화”라고 한다.
public synchronized void clacSum(){} // 메서드 전체를 임계영역으로 정한다.
// 호출 시점부터 포함 객체의 lock을 얻어 작업을 수행한다.
synchronized(객체의 참조 변수){} // 임계영역을 정한다.
// 호출 시점부터 포함 객체의 lock을 얻어 작업을 수행한다.
모든 객체는 lock을 하나씩 가지고 있으며, 해당 객체의 lock을 가진 스레드만 임계영역의 코드를 수행가능하다.
public class ThreadEx21 {
public static void main(String args[]) {
Runnable r = new RunnableEx21();
new Thread(r).start();
new Thread(r).start();
}
}
class Account{
private int balance = 1000;
public int getBalance(){
return balance;
}
public void withdraw(int money){
if(balance >= money){
try{
Thread.sleep(1000);
}catch (InterruptedException e){}
balance-=money;
}
}
}
class RunnableEx21 implements Runnable{
Account acc = new Account(); // Account를 하나 생성하기 때문에 Account 내부에 balance 데이터를 공유한다.
public void run(){
while(acc.getBalance() > 0){
int money = (int) (Math.random() *3 + 1) * 100;
acc.withdraw(money);
System.out.println("balance:"+acc.getBalance());
}
}
}
public synchronized void withdraw(int money){ // 임계영역을 정해준다.
if(balance >= money){
try{
Thread.sleep(1000);
}catch (InterruptedException e){}
balance-=money;
}
}
'개발' 카테고리의 다른 글
[스프링] DI (Dependency Injection) (0) | 2022.04.18 |
---|---|
[Java] static과 final의 차이가 뭘까?(feat. JVM 메모리) (2) | 2022.03.07 |
[TDD] 자바와 JUnit을 활용한 실용주의 단위 테스트 (작성중) (0) | 2022.02.24 |
[JUnit5] JUnit5를 공부해보자(Spring Boot, REST API) (0) | 2022.02.24 |
JAVA의 Servlet이 뭐지? (0) | 2022.02.19 |