쓰레드 thread

LeeKyoungChang·2022년 3월 13일
0
post-thumbnail

Java의 정석 의 책을 읽고 정리한 내용입니다.

 

📚 1. 프로세스와 쓰레드

✏️ 프로세스란?
프로세스란 간단히 말해서 실행 중인 프로그램이다.
멀티쓰레드 프로세스(multi-threaded process) : 둘 이상의 쓰레드를 가진 프로세스

 

💡 참고
쓰레드를 프로세스라는 작업공간(공장)에서 작업을 처리하는 일꾼으로 생각하자!

 

✔️ 멀티쓰레딩의 장점

  • CPU의 사용률을 향상시킨다.
  • 자원을 보다 효율적으로 사용할 수 있다.
  • 사용자에 대한 응답성이 향상된다.
  • 작업이 분리되어 코드가 간결해진다.

 

💡 참고

  • 쓰레드를 가벼운 프로세스 즉, 경량 프로세스(LWP)라고 부르기도 한다.
  • 교착상태란 두 쓰레드가 자원을 점유한 상태에서 서로 상대편이 점유한 자원을 사용하려고 기다리느라 진행이 멈춰 있는 상태를 말한다.

 

📚 2. 쓰레드의 구현과 실행

  • 쓰레드를 구현하는 방법은 Thread클래스를 상속받는 방법과 Runnable 인터페이스를 구현하는 방법, 모두 2가지가 있다.
  • Thread 클래스를 상속받으면 다른 클래스를 상속받을 수 없기 때문에, Runnable 인터페이스를 구현하는 방법이 일반적이다.
  • Runnable 인터페이스를 구현하는 방법은 재사용성(reusability)이 높고 코드의 일관성(consistency)을 유지할 수 있기 때문에 보다 객체지향적인 방법이라 할 수 있다.

 

1) Thread 클래스를 상속
class MyThread extends thread{
	public void run(){/*작업 내용*/}// Thread 클래스의 run()을 오버라이딩

}

2) Runnable 인터페이스를 구현
class MyThread implements Runnable{
	public void run(){/*작업 내용*/}// Runnable인터페이스의 추상메서드 run()을 구현
}
  • Runnable 인터페이스는 run()메서드만 정의되어 있는 간단한 인터페이스이다. Runnable 인터페이스를 구현하기 위해서 해야 할 일은 추상 메서드인 run()의 몸통{}을 만들어 주는 것뿐이다.
public interface Runnable{
	public abstract void run();
}

 

Thread클래스를 상속받으면, 자손 클래스에서 조상인 Thread클래스의 메서드를 직접 호출할 수 있지만, Runnable을 구현하면 Thread클래스의 static메서드인 currentThread()를 호출하여 쓰레드에 대한 참조를 얻어 와야만 호출이 가능하다.

static Thread currentThread() - 현재 실행중인 쓰레드의 참조를 반환한다.
String getName() - 쓰레드의 이름을 반환한다.

Runnablerun()밖에 없기 때문에 Thread클래스의 getName()을 호출하려면 Thread.currentThread().getName()와 같이 해야한다.

class ThreadEx implements Runnable{
	public void run(){
		for(int i = 0; i < 5; i++){
			// Thread.currentThread() - 현재 실행중인 Thread를 반환한다.
			System.out.println(Thread.currentThread().getName());
		}
	}
}

 

✔️ 쓰레드의 실행 - start()

  • 한 번 실행이 종료된 쓰레드는 다시 실행할 수 없다.
  • 하나의 쓰레드에 대해 start()가 한 번만 호출될 수 있다.
  • 쓰레드의 작업을 한 번 더 수행해야 한다면 새로운 쓰레드를 생성한 다음에 start()를 호출해야 한다.
  • 하나의 쓰레드에 대해 start()를 두 번 이상 호출하면 실행시에 IllegalThreadStateException이 발생한다.
ThreadEx1_1 t1 = new ThreadEx1_1();
t1.start();
t1.start(); // 예외 발생

ThreadEx1_1 t1 = new ThreadEx1_1();
t1.start();
t1 = new ThreadEx1_1();	// 다시 생성
t1.start();	// OK

 

📚 3. start()와 run()

사진1

1. main메서드에서 쓰레드의 start()를 호출한다.
2. start()는 새로운 쓰레드를 생성하고, 쓰레드가 작업하는데 사용될 호출스택을 생성한다.
3. 새로 생성된 호출스택에 run()이 호출되어, 쓰레드가 독립된 공간에서 작업을 수행한다.
4. 이제는 호출스택이 2개가 되므로 스케쥴러가 정한 순서에 의해서 번갈아 가면서 실행된다.

 

✔️ main쓰레드

  • main메서드의 작업을 수행하는 것도 쓰레드이며, 이를 main쓰레드라고 한다.
  • 프로그램을 실행하면 기본적으로 하나의 쓰레드(일꾼)을 생성하고, 그 쓰레드가 main메서드를 호출해서 작업이 수행되도록 하는 것이다.
  • 실행 중인 사용자 쓰레드가 하나도 없을 때 프로그램은 종료된다.

 

📚 4. 싱글쓰레드와 멀티쓰레드

💡 참고
컨텍스트 스위칭(context switching) : 프로세스 또는 쓰레드 간의 작업 전환

 

✔️ 싱글코어와 멀티코어의 비교

KakaoTalk_Photo_2022-03-13-16-09-27 001

  • 실행결과를 싱글 코어일 때와 멀티 코어일 때를 비교해 놓았는데, 싱글 코어인 경우에는 멀티쓰레드라도 하나의 코어가 번갈아가면서 작업을 수행하는 것이므로 두 작업이 절대 겹치지 않는다.
  • 그러나, 멀티 코어에서는 멀티쓰레드로 두 작업을 수행하면, 동시 두 쓰레드가 수행될 수 있으므로 두 작업 A와 B가 겹치는 부분이 발생한다.

 

💡 참고

  • 병행(concurrent) : 여러 쓰레드가 여러 작업을 동시에 진행하는 것
  • 병렬(parallel) : 하나의 작업을 여러 쓰레드가 나눠서 처리하는 것

 

✔️ 싱글쓰레드 프로세스 (위)와 멀티쓰레드 프로세스(아래)의 비교

KakaoTalk_Photo_2022-03-13-16-09-27 002

  • 만일 사용자로부터 입력받는 작업(A)과 화면에 출력하는 작업(B)을 하나의 쓰레드로 처리한다면 사용자가 입력을 마칠 때까지 아무 일도 하지 못하고 기다려야 한다.
  • 두 개의 쓰레드로 처리한다면 사용자의 입력을 기다리는 동안 다른 쓰레드가 작업을 처리할 수 있기 때문에 보다 효율적인 CPU의 사용이 가능하다!

 

📚 5. 쓰레드의 우선순위

  • 우선순위의 값에 따라 쓰레드가 얻는 실행시간이 달라진다.
  • 쓰레드가 수행하는 작업의 중요도에 따라 쓰레드의 우선순위를 서로 다르게 지정하여 특정 쓰레드가 더 많은 작업시간을 갖도록 할 수 있다.
  • 시각적인 부분이나 사용자에게 빠르게 반응해야하는 작업을 하는 쓰레드의 우선 순위는 다른 작업을 수행하는 쓰레드에 비해 높아야 한다.

 

✔️ 쓰레드의 우선순위 지정하기

void setPriority(int newPriority) : 쓰레드의 우선순위를 지정한 값으로 변경한다.

int getPriority() : 쓰레드의 우선순위를 반환한다.


public static final int MAX_PRIORITY = 10 // 최대 우선 순위
public static final int MIN_PRIORITY = 1 // 최소 우선 순위
public static final int NORM_PRIORITY = 5 // 보통 우선 순위
  • 쓰레드가 가질 수 있는 우선순위의 범위는 1~10이며 숫자가 높을 수록 우선순위가 높다.
  • 쓰레드의 우선순위는 쓰레드를 생성한 쓰레드로부터 상속 받는다는 것이다.
  • main 메서드를 수행하는 쓰레드는 우선순위가 5이므로 main 메서드 내에서 생성하는 쓰레드의 우선순위는 자동적으로 5가 된다.
class ThreadPriority {
	public static void main(String args[]) {

		A th1 = new A();
		B th2 = new B();
		
		th1.setPriority(7); // defalut 우선순위 5
		System.out.println("Priority of th1(-) : " + th1.getPriority() );
		System.out.println("Priority of th2(|) : " + th2.getPriority() );
		th1.start();
		th2.start();
	}
}

class A extends Thread {
	public void run() {
		for(int i=0; i < 300; i++) {
			System.out.print("-");
			for(int x=0; x < 10000000; x++);
		}
	}
}

class B extends Thread {
	public void run() {
		for(int i=0; i < 300; i++) {
			System.out.print("|");
			for(int x=0; x < 10000000; x++);
		}
	}	
}

Priority of th1(-) : 5
Priority of th2(|) : 7
  • th1th2 모두 main 메서드에서 생성하였기 때문에 main 메서드를 실행하는 쓰레드의 우선순위인 5를 상속받았다.
  • 그 다음에는 th2.setPriority(7)th2의 우선순위를 7로 변경한 다음에 start()를 호출해서 쓰레드를 실행시켰다.
  • 이처럼 쓰레드를 실행하기 전에만 우선 순위를 변경할 수 있다는 것을 기억하자.
  • 우선순위가 높은 th2의 실행시간이 th1에 비해 상당히 늘어났다.

 

📚 6. 쓰레드 그룹(thread group)

  • 쓰레드 그룹은 서로 관련된 쓰레드를 그룹으로 다루기 위한 것으로, 폴더를 생성해서 관련된 파일들을 함께 넣어서 관리하는 것처럼 쓰레드 그룹을 생성해서 쓰레드를 그룹으로 묶어서 관리할 수 있다.
생성자/메소드설명
ThreadGroup(String name)지정된 이름의 새로운 쓰레드 그룹을 생성
ThreadGroup(ThreadGroup parent, String name)지정된 쓰레드 그룹에 포함되는 새로운 쓰레드 그룹 생성
int activeCount()쓰레드 그룹에 포함된 활성상태에 있는 쓰레드의 수를 반환
int activeGroupCount()쓰레드 그룹에 포함된 활성상태에 있는 쓰레드 그룹의 수를 반환
void checkAccess()현재 실행중인 쓰레드가 쓰레드 그룹을 변경할 권한이 있는지 체크. 만일 권한이 없다면 SecurityException을 발생시킨다.
void destroy()쓰레드 그룹과 하위 쓰레드 그룹까지 모두 삭제한다. 단, 쓰레드 그룹이나 하위 쓰레드 그룹이 모두 비어있어야 한다.
int enumerate(Thread[] list) int enumerate(Thread[] list, boolean recurse) int enumerate(ThreadGroup[] list) int enumerate(ThreadGroup[] list, boolean recurse)쓰레드 그룹에 속한 쓰레드 또는 하위 쓰레드 그룹의 목록을 지정된 배열에 담고 그 개수를 반환 두 번째 매개변수인 recurse의 값을 true로 하면 하위 쓰레드 그룹에 쓰레드 또는 쓰레드 그룹까지 배열에 담는다.
int getMaxPriority()쓰레드 그룹의 최대우선순위를 반환
String getName()쓰레드 그룹의 이름을 반환
ThreadGroup getParent()쓰레드 그룹의 상위 쓰레드그룹을 반환
void interrupt()쓰레드 그룹에 속한 모든 쓰레드를 interrupt
boolean isDaemon()쓰레드 그룹이 데몬 쓰레드그룹인지 확인
boolean isDestroyed()쓰레드 그룹이 삭제되었는지 확인
void list()쓰레드 그룹에 속한 쓰레드와 하위 쓰레드그룹에 대한 정보를 출력
boolean parentOf(ThreadGroup g)지정된 쓰레드 그룹의 상위 쓰레드그룹인지 확인
void setDeamon(boolean daemon)쓰레드 그룹을 데몬 쓰레드그룹으로 설정/해제
void setMaxPriority(int pri)쓰레드 그룹의 최대우선순위를 설정

 

✔️ 쓰레드를 쓰레드 그룹에 포함시키는 방법

Thread(ThreadGroup group, String name)
Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
  • 모든 쓰레드는 반드시 쓰레드 그룹에 포함되어 있어야 한다.
  • 우리가 생성하는 모든 쓰레드 그룹은 main 쓰레드 그룹의 하위 쓰레드 그룹이 되며, 쓰레드 그룹을 지정하지 않고 생성한 쓰레드는 자동적으로 main 쓰레드 그룹에 속하게 된다.

 

✔️ 그 외에 Thread의 쓰레드 그룹과 관련된 메서드

ThreadGroup getThreadGroup() // 쓰레드 자신이 속한 쓰레드 그룹을 반환한다.
void uncaughtException(Thread t, Throwable e) // 쓰레드 그룹의 쓰레드가 처리되지 않은 예외에 의해 실행이 종료되었을 때, JVM에 의해 이 메서드가 자동적으로 호출된다.

 

📚 7. 데몬 쓰레드(daemon thread)

  • 데몬 쓰레드는 다른 일반 쓰레드(데몬 쓰레드가 아닌 쓰레드)의 작업을 돕는 보조적인 역할을 수행하는 쓰레드이다.
  • 일반 쓰레드가 모두 종료되면 데몬 쓰레드는 강제적으로 자동 종료되는데, 그 이유는 데몬 쓰레드는 일반 쓰레드의 보조 역할을 수행하므로 일반 쓰레드가 모두 종료되고 나면 데몬 쓰레드의 존재의 의미가 없기 때문이다.
  • 이 점을 제외하고는 데몬 쓰레드와 일반 쓰레드는 다르지 않다.
  • 데몬 쓰레드의 예로는 가비지 컬렉션, 워드 프로세서의 자동저장, 화면 자동갱신 등이 있다.
  • 데몬 쓰레드는 무한 루프와 조건문을 이용해서 실행 후 대기하고 있다가 특정 조건이 만족되면 작업을 수행하고 다시 대기하도록 작성한다.
  • 데몬 쓰레드는 일반 쓰레드의 작성 방법과 실행 방법이 같으며 다만 쓰레드를 생성한 다음 실행하기 전에 setDaemon(true)를 호출하기만 하면 된다.
  • 그리고 데몬 쓰레드가 생성한 쓰레드는 자동적으로 데몬 쓰레드가 된다.
boolean isDaemon()  // 쓰레드가 데몬 쓰레드인지 확인한다. 데몬 쓰레드이면 true 반환한다.
void setDaemon(boolean on)  // 쓰레드를 데몬 쓰레드 또는 사용자 쓰레드로 변경한다. 매개변수 on의 값을 true로 지정하면 데몬 쓰레드가 된다.

class ThreadEx implements Runnable{
    static boolean autoSave = false;
	
    public static void main(String[] args) {
        Thread t = new Thread(new ThreadEx());
        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(3 * 1000);
            }catch(InterruptedException e){}             

            if(autoSave)
                autoSave();
        }
    }

    private void autoSave() {
        System.out.println("작업파일이 자동저장되었습니다.");
    }
}
1
2
3
4
5
작업파일이 자동저장되었습니다.
6
7
8
작업파일이 자동저장되었습니다.
9
10
프로그램을 종료합니다.

setDaemon 메서드는 반드시 start()를 호출하기 전에 실행되어야 한다. 그렇지 않으면 IllegalThreadStateException이 발생한다.

 

💡 참고

  • getAllStackTraces()를 이용하면 실행 중 또는 대기상태, 즉 작업이 완료되지 않은 모든 쓰레드의 호출스택을 출력할 수 있다.
  • 프로그램을 실행하면, JVM은 가비지컬렉션, 이벤트처리, 그래픽처리와 같이 프로그램이 실행되는데 필요한 보조작업을 수행하는 데몬 쓰레드들을 자동적으로 생성해서 실행시킨다.
  • 이들은 system 쓰레드 그룹 또는 main 쓰레드 그룹에 속한다.

 

📚 8. 쓰레드의 실행제어

  • 효율적인 멀티쓰레드 프로그램을 만들기 위해서는 보다 정교한 스케줄링을 통해 프로세스에게 주어진 자원과 시간을 여러 쓰레드가 낭비없이 잘 사용하도록 프로그래밍 해야 한다.

 

✔️ 쓰레드의 스케줄링과 관련된 메서드

메서드설명
static void sleep(long millis), static void sleep(long millis, int nanos)지정된 시간(천분의 일초 단위)동안 쓰레드를 일시정지시킨다. 지정한 시간이 지나고 나면, 자동적으로 다시 실행대기 상태가 된다.
void join(), void join(long millis), void join(long millis, int nanos)지정된 시간동안 쓰레드가 실행되도록 한다. 지정된 시간이 지나거나 작업이 종료되면 join()을 호출한 쓰레드로 다시 돌아와 실행을 계속한다.
void interrupt()sleep()이나 join()에 의해 일시정지상태인 쓰레드를 깨워서 실행대기 상태로 만든다. 해당 쓰레드에서는 interruptedException이 발생함으로써 일시정지 상태를 벗어나게 된다.
void stop()쓰레드를 즉시 종료시킨다.
void suspend()쓰레드를 일시정지 시킨다. resume()을 호출하면 다시 실행대기 상태가 된다.
void resume()suspend()에 의해 일시정지상태에 있는 쓰레드를 실행대기 상태로 만든다.
static void yield()실행 중에 자신에게 주어진 실행시간을 다른 쓰레드에게 양보(yield)하고 자신은 실행대기상태가 된다.

 

✔️ 쓰레드의 상태

상태설명
NEW쓰레드가 생성되고 아직 start()가 호출되지 않은 상태
RUNNABLE실행 중 또는 실행 가능한 상태
BLOCKED동기화블럭에 의해서 일시정지된 상태(lock이 풀릴 때까지 기다림)
WAITING, TIMED_WAITING쓰레드의 작업이 종료되지는 않았지만 실행가능하지 않은(unrunnable) 일시정지 상태. TIMED_WAITING은 일시정지시간이 지정된 경우를 의미한다.
TERMINATED쓰레드의 작업이 종료된 상태

 

💡 참고
쓰레드의 상태는 ThreadgetState()메서드를 호출해서 확인할 수 있다. JDK1.5부터 추가되었다.

 

✔️ 쓰레드의 상태가 어떻게 변화되는지 알아보자!

스크린샷 2022-03-13 오후 5 12 29
  1. 쓰레드를 생성하고 start()를 호출하면 바로 실행되는 것이 아니라 실행대기열에 저장되어 자신의 차례가 될 때까지 기다려야 한다. 실행대기열은 큐(queue)와 같은 구조로 먼저 실행대기열에 들어온 쓰레드가 먼저 실행된다.
  2. 실행대기상태에 있다가 자신의 차례가 되면 실행상태가 된다.
  3. 주어진 실행시간이 다되거나 yield()를 만나면 다시 실행대기상태가 되고 다음 차례의 쓰레드가 실행상태가 된다.
  4. 실행 중에 suspecd(), sleep(), wait(), join(), I/O block에 의해 일시정지상태가 될 수 있다. I/O block은 입출력작업에서 발생하는 지연상태를 말한다. 사용자의 입력을 기다리는 경우를 예로 들 수 있는데, 이런 경우 일시정지 상태에 있다가 사용자가 입력을 마치면 다시 실행대기상태가 된다.
  5. 지정된 일시정지시간이 다되거나(time-out), notify(), resume(), interrupt()가 호출되면 일시정지 상태를 벗어나 다시 실행대기열에 저장되어 자신의 차례를 기다리게 된다.
  6. 실행을 모두 마치거나 stop()이 호출되면 쓰레드는 소멸된다.

 

💡 참고
번호의 순서대로 쓰레드가 수행되는 것은 아니다.

 

✔️ Sleep(long mills) - 일정시간 동안 쓰레드를 멈추게 한다.

static void sleep(long mills)	//static = 자기자신
static void sleep(long mills, int nanos)	//static = 자기자신
try{
	Thread.sleep(1, 500000);	// 쓰레드를 0.0015초 동안 멈추게 한다.
}catch(InterruptedException e){}
  • sleep()에 의해 일시정지 상태가 된 쓰레드는 지정된 시간이 다되거나 interrupt()가 호출되면(InterruptedException 발생), 잠에서 깨어나 실행대기 상태가 된다.
  • sleep()을 호출할 때는 항상 try-catch문으로 예외를 처리해줘야 한다.

 

th1.start();
th2.start();

try{
	th1.sleep(2000);
}catch(InterruptedException e){}

System.out.println("<<main 종료>>");
  • sleep()이 항상 현재 실행 중인 쓰레드에 대해 작동하기 떄문에 th1.sleep(2000)과 같이 호출 하였어도 실제로 영향을 받는 것은 main메서드를 실행하는 main쓰레드 이다.
  • sleep()Thread.sleep(2000) 과 같이 해야 한다.

 

✔️ interrupt()와 interrupted() - 쓰레드의 작업을 취소한다.

  • interrupt()는 쓰레드에게 작업을 멈추라고 요청한다. 단지 멈추라고 요청만 하는 것일 뿐 쓰레드를 강제로 종료시키지는 못한다.
  • interrupt()는 그저 쓰레드의 interrupted 상태(인스턴스 변수)를 바꾸는 것일 뿐이다.
  • interrupted()는 쓰레드에 대해 interrupt()가 호출되었는지 알려준다.
  • interrupt()가 호출되지 않았다면 false를, 호출되었다면 true를 반환한다.

 

Thread th = new Thread();
th.start();
// ...
th.interrupt(); // 쓰레드 th에 interrupt()를 호출한다.

class MyThread extends Thread{
	public void run(){
		while(!interrupted()){ // interrupted()의 결과가 false인 동안 반복
			...
		}
	}
}
void interrupt() : Thread의 interrupted 상태를 false에서 true로 변경
boolean interrupted() : Thread interrupted 상태를 반환
static boolean interrupted() : 현재 Thread의 interrupted 상태를 반환후, false로 변경

 

✔️ suspend(), resume(), stop()

  • suspend()sleep()처럼 쓰레드를 멈추게 한다.
  • suspend()에 의해 정지된 쓰레드는 resume()을 호출해야 다시 실행대기 상태가 된다.
  • stop()은 호출되는 즉시 쓰레드가 종료된다.
  • suspend()stop()이 교착상태(deadlock)를 일으키기 쉽게 작성되어 있으므로 사용이 권장되지 않는다. 그래서 이 메서드들은 모두 deprecated 되었다.
  • deprecated : 전에는 사용되었지만, 앞으로 사용하지 않을 것을 권장한다.
  • deprecated된 메서드는 하위 호환성을 위해서 삭제하지 않는 것일 뿐이므로 사용해서는 안된다.

 

✔️ yield() - 다른 쓰레드에게 양보한다.

  • yield()는 쓰레드 자신에게 주어진 실행시간을 다음 차례의 쓰레드에게 양보(yield)한다.
  • 예를 들어 스케쥴러에 의해 1초의 실행시간을 할당받은 쓰레드가 0.5초의 시간동안 작업한 상태에서 yield()가 호출되면, 나머지 0.5초는 포기하고 다시 실행대기상태가 된다.
  • yield()interrupt()를 적절히 사용하면, 프로그램의 응답성을 높이고 보다 효율적인 실행이 가능하게 할 수 있다.

 

✔️ join() - 다른 쓰레드의 작업을 기다린다.

void join()
void join(long millis)
void join(long millis, int nanos)
  • 쓰레드 자신이 하던 작업을 잠시 멈추고 다른 쓰레드가 지정된 시간동안 작업을 수행하도록 할 때 join()을 사용한다.
  • 시간을 지정하지 않으면, 해당 쓰레드가 작업을 모두 마칠 때까지 기다리게 된다.
  • 작업 중에 다른 쓰레드의 작업이 먼저 수행되어야할 필요가 있을 때 join()을 사용한다.
try{
	th1.join(); // 현재 실행중인 쓰레드가 쓰레드의 th1의 직업이 끝날때까지 기다린다.
} catch(InterruptedException e){}
  • join()sleep()처럼 interrupt()에 의해 대기상태에서 벗어날 수 있으며, join()이 호출되는 부분을 try-catch문으로 감싸야 한다.
  • join()은 여러모로 sleep()과 유사한 점이 많은데, sleep()과 다른 점은 join()은 현재 쓰레드가 아닌 특정 쓰레드에 대해 동작하므로 static메서드가 아니라는 것이다.

 

  • 가비지 컬렉터와 같은 데몬 쓰레드의 우선순위를 낮추기 보다는 sleep()을 이용해서 주기적으로 실행되도록 하다가 필요할 때마다 interrupt()를 호출해서 즉시 가비지 컬렉션이 이루어지도록 하는 것이 좋다.
  • 그리고 필요하다면 join()도 함께 사용해야한다는 것을 기억하자!

 

profile
"야, (오류 만났어?) 너두 (해결) 할 수 있어"

0개의 댓글

관련 채용 정보