25장 쓰레드는 개발자라면 알아두는 것이 좋아요
- 쓰레드와 프로세스의 차이는?
- JVM이 시작되면 실행되는 것이 프로세스, 프로세스 안에 서 하나 이상의 쓰레드가 수행
- 쓰레드 클래스르 만들기 위해서는 어떤 인터페이스를 구현하는가?
- Runnable
- 인터페이스에 선언된 유일한 메소드는?
- run()
- 쓰레드 클래스를 만들기 위해서 어떤 클래스를 확장하는가?
- Thread
- 쓰레드가 시작되는 메소드의 이름은 무엇인가?
- run()
- 쓰레드를 시작하는 메소드의 이름은 무엇인가?
- start()
- 쓰레드에 선언되어 있는 sleep() 메소드의 역할은?
- 매개 변수로 넘어온 시간(1/1,000초)만큼 대기
- sleep() 메소드를 사용할 때는 try-catch로 감싸 주어 예외를 처리해야하는데, 그 이유는 ?
- sleep() 메소드는 InterruptedException을 던질 수 있다고 선언되어 있기 때문
- Daemon 데몬쓰레드와 일반 쓰레드의 차이점?
- 데몬쓰레드는 그 쓰레드가 수행되고 있든, 아니든 상관없이 JVM이 끝남
- 일반 쓰레드는 종료할 때까지 수행
- synchronized 구문은 왜 사용하며, 어디에 사용해야 하는가?
- 여러쓰레드가 한 객체에 선언된 메소드에 접근하여 데이터를 처리하려고 할 때, 동시에 연산을 수행하여 값이 꼬이는 경우가 발생하지 않도록 쓰레드 안전하게 하기 위함
- synchronized를 사용하는 두가지 방법?
- 메소드 자체를 synchronized로 선언하는 방법
- 메소드 내의 특정 문장만 synchronized로 감싸는 방법
- 쓰레드의 상태는 어떤 것들이 있는지?
- NEW, RUNNABLE, BLOCKED, WAITING, TIME_WAITING, TERMINATED
- 쓰레드에 선언되어있는 join()메소드의 용도는?
- 수행중인 쓰레드가 중지할 때까지 대기
- 쓰레드에 선언되어 있는 interrupt() 메소드의 용도는?
- 수행중인 쓰레드에 중지 요청
- interrrupt() 메소드를 호출하면 해당 쓰레드는 어떤 상태에 있을 때, interrupt() 메소드가 호출한 효과가 발생하는가?
- Object 클래스에 선언된 wait()메소드의 용도는?
- 다른 스레드가 Object 객체에 대한 notify() 메소드나 notifyAll() 메소드를 호출할 때까지 현재 쓰레드가 대기하고 있도록함
- Object 클래스에 선언된 notify()메소드의 용도는?
- Object 객체의 모니터에 대기하고 있는 단일 스레드를 깨우는 용도
- ThreadGroup 클래스에 선언된 enumerate() 메소드의 용도는?
- 현재 쓰레드 그룹에 있는 모든 쓰레드를 매개 변수로 넘어온 쓰레드 배열에 담는 용도
쓰레드란?
스레드(thread)란 프로세스(process) 내에서 실제로 작업을 수행하는 주체를 의미한다. 모든 프로세스에는 한 개 이상의 스레드가 존재하여 작업을 수행한다.
쓰레드를 생성하는 두가지 방법
- Runnable 인터페이스 구현 - run() 메소드 하나만 존재
- Thread 클래스 사용 - 매우 많은 생성자와 메소드 제공
=> 두가지 모두 java.lang패키지에 있다.
public class RunnableSample implements Runnable{
@Override
public void run() {
System.out.println("this is runnable's run() method");
}
}
public class ThreadSample extends Thread{
public void run(){
System.out.println("this is threadsample's run() method");
}
}
public class RunThreads {
public static void main(String[] args){
RunThreads threads = new RunThreads();
threads.runCommonThread();
}
private void runBasic() {
RunnableSample rs = new RunnableSample();
new Thread(rs).start();
ThreadSample ts = new ThreadSample();
ts.start();
System.out.println("RunThreads.runBasic() method is ended");
}
}
- 쓰레드가 수행되는 우리가 구현하는 메소드는 run()
- 쓰레드를 시작하는 메소드는 start()
우리가 쓰레드를 구현할 때, start()메소드를 호출하면 쓰레드 클래스에 있는 run() 메소드의 내용이 끝나는 아니든, 쓰레드를 시작한 메소드에서는 그 다음 줄 코드를 실행한다.
public class RunMultiThreads {
public static void main(String[] args){
RunMultiThreads rm = new RunMultiThreads();
rm.runMultiThreads();
}
public void runMultiThreads() {
RunnableSample[] runnable = new RunnableSample[5];
ThreadSample[] thread = new ThreadSample[5];
for (int i = 0; i < 5; i++) {
runnable[i] = new RunnableSample();
thread[i] = new ThreadSample();
new Thread(runnable[i]).start();
thread[i].start();
}
System.out.println("RunMultiThreads.runMultiThread() method is ended");
}
}
결과
this is runnable's run() method
this is threadsample's run() method
this is runnable's run() method
RunMultiThreads.runMultiThread() method is ended
this is threadsample's run() method
this is runnable's run() method
this is threadsample's run() method
this is runnable's run() method
this is threadsample's run() method
this is runnable's run() method
this is threadsample's run() method
=> 새로 생성한 쓰레드는 run() 메소드가 종료되면 끝난다. 만약 run() 메소드가 끝나지 않으면 우리가 실행한 어플리케이션은 끝나지 않는다
public class NameCalcThread extends Thread{
private int calcNumber;
public NameCalcThread(String name, int calcNumber){
super(name);
this.calcNumber = calcNumber;
}
public void run(){
calcNumber++;
System.out.println(calcNumber);
}
}
모든 쓰레드에는 이름이 있는데, 이름을 지정하지 않으면 그 쓰레드의 이름은 Thread-n이다
지정하면 별도의 이름을 가지는데, 쓰레드 이름이 겹쳐도 예외나 에러가 발생하지 않는다.
=> 이름을 매개변수로 받아 이름을 세팅한다.
sleep() 메소드
- InterruptedException을 던질수도 있다고 선언되어 있기 때문에, 항상 try-catch로 묶어줘야한다.
- 자기 자신을 종료할 수없고, 외부의 자극으로 종료되야하기 때문에 static으로 선언되어 있다
리턴 타입 | 메소드 이름 및 매개 변수 | 설명 |
static void | sleep(long millis) | 매개 변수로 넘어온 시간(1/1,000초)만큼 대기 |
static void | sleep(long millis, int nanos) | 첫번째 매개 변수로 넘어온 시간(1/1,000초)+ 두번째 매개변수로 넘어온 시간(1/1,000,000,000초) 만큼 대기 |
public class EndlessThread extends Thread{
public void run(){
while (true){
try {
System.out.println(System.currentTimeMillis());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class RunThreads {
public static void main(String[] args){
RunThreads threads = new RunThreads();
threads.runBasic();
}
private void runBasic() {
EndlessThread et = new EndlessThread();
et.start();
}
}
run() 메소드의 경우 While(true)이기 때문에 다음과 같이 지속적으로 출력된다.
1683108562446
1683108563447
1683108564447
=> 즉, main()메소드의 수행이 끝나더라도 다른 메소드에서 시작한 쓰레드가 종료되지 않으면 해당 자바 프로세스는 끝나지 않는다.
주요 메소드
리턴 타입 | 메소드 이름 및 매개 변수 | 설명 |
void | run() | 우리가 구현해야 하는 메소드 |
long | getId() | 스레드의 고유 Id 리턴한다. jvm에서 자동 생성해줌 |
String | getName() | 스레드의 이름을 리턴한다 |
void | setName(String name) | 스레드의 이름을 지정한다 |
int | getPriority() | 스레드의 우선 순위 확인한다 |
void | setPriority(int newPriority) | 스레드의 우선 순위 지정한다 |
boolean | isDaemon() | 스레드를 데몬으로 설정할지 아닌지를 설정 |
void | setDaemon(boolean on) | 스레드를 데몬으로 설정할지 아닌지를 설정 |
StackTraceElement[] | getStackTrace() | 스레드의 스택 정보 확인 |
Thread.State | getState() | 스레드의 상태 확인 |
ThreadGroup | getThreadGroup() | 스레드의 그룹 확인 |
- 우선 순위의 기본값은 5
데몬 스레드
데몬으로 지정하면 프로그램이 대기없이 그냥 끝난다.
즉, 데몬 쓰레드는 해당 쓰레드가 종료되지 않아도 다른 실행중인 일반 쓰레드가 없다면 멈춰버린다.
데몬 스레드를 사용하는 이유
=> 모니터링하는 스레드를 별도로 띄워 모니터링 하다가, 주요 쓰레드가 종료되면 관련된 모니터링 쓰레드가 종료되어야 프로세스가 종료될 수 있다. 만약 모니터링 스레드를 데몬 스레드로 만들지 않으면 프로세스가 종료할 수 없다.
예로는, 크롬 = 주요스레드가 되고 크롬안의 유투브, 블로그등이 각각의 스레드가 될것이고, 크롬을 종료하면 이를 모니터링하고 있는 데몬스레드가 유투브와 블로그를 종료할 것이다.
synchronized
Thread safe하기 위해 사용하는 예약어로, 여러 개의 스레드에서 하나의 객체에 있는 인스턴스 변수를 동시에 처리할 때 발생할 수 있는 문제를 해결하기 위해 필요하다.
// 같은 객체를 참조하므로 유효
CommonCalculate calc = new CommonCalculate();
ModifyAmountThread thread1 = ModifyAmountThread(calc, true);
ModifyAmountThread thread2 = ModifyAmountThread(calc, true);
// 다른 객체를 참조하므로 유효하지 않음
CommonCalculate calc1 = new CommonCalculate();
ModifyAmountThread thread1 = ModifyAmountThread(calc1, true);
CommonCalculate calc2 = new CommonCalculate();
ModifyAmountThread thread2 = ModifyAmountThread(calc2, true);
synchronized의 두가지 사용 방법
1.메소드 자체를 synchronized로 선언
public synchronized void plus(int value){
amount+=value;
}
문제 => 어떤 스레드에서 plus()메소드를 수행하고 있을 때, 다른 스레드에서 수행하려고 하면 늦게온 스레드는 앞서 수행하는 메소드가 끝날 때까지 기다려야 한다. 이렇게 하면 성능상의 문제점이 발생할 수 있다.
2. 메소드 내의 특정 문장만 synchronized로 감싸는 방법
- this나 lock 객체는 문지기라고 생각하면 된다. 그 문지기는 한 명의 스레드만 일을 하도록 허용
public void plus(int value){
synchronized(this)
amount+=value;
}
Object lock = new Object();
public void plus(int value){
synchronized(lock)
amount+=value;
}
=> 이렇게 하면 synchronize(this or lock) 이후에 있는 중괄호 내에 있는 연산만 여러 스레드에서 처리하지 않겠다는 뜻으로 보다 효율적이다
스레드를 통제하는 메소드
리턴 타입 | 메소드 이름 및 매개 변수 | 설명 |
Thread.State | getState() | 스레드의 상태 확인 |
void | join() | 수행 중인 스레드가 중지할 때까지 대기 |
void | join(long millis) | 매개 변수에 지정된 시간만큼(1/1,000초) 대기 |
void | join(long millis, int nanos) | 첫번째 매개 변수로 넘어온 시간(1/1,000초)+ 두번째 매개변수로 넘어온 시간(1/1,000,000,000초) 만큼 대기 |
void | interrupt() | 수행중인 스레드에 중지요청 |
- join() 메소드는 InterruptedException을 발생시키며 중단
- join() 메소드의 첫번째 매개변수가 음수거나 두번째 매개변수가 0~999,999사이의 값이 아닌경우 IllegalArgumentException발생
- interrupt()메소드는 sleep(), join()와 또는 wait() 같이 대기상태를 메소드가 호출되었을때 interrupt()메소드 호출 가능
Thread.State (NEW -> 상태 -> TERMINATED)
NEW | 스레드 객체는 생성되었지만, 아직 시작되지는 않은 상태 |
RUNNABLE | 스레드가 실행중인 상태 |
BLOCKED | 스레드가 실행 중지 상태이며, 모니터 락이 풀기기를 기다리는 상태 |
WAITING | 스레드가 대기중인 상태 |
TIME_WAITING | 특정 시간만큼 스레드가 대기중인 상태 |
TERMINATED | 스레드가 종료된 상태 |
public class RunSupportThreads {
public static void main(String[] args){
RunSupportThreads rst = new RunSupportThreads();
rst.checkThreadState1();
}
public void checkThreadState1() {
SleepThread thread = new SleepThread(2000);
try {
System.out.println("thread state=" + thread.getState());
thread.start();
System.out.println("thread state(after state)=" + thread.getState());
Thread.sleep(1000);
System.out.println("thread state(after 1sec)=" + thread.getState());
thread.join();
thread.interrupt();
System.out.println("thread state(after join)="+thread.getState());
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
결과
thread state=NEW
thread state(after state)=RUNNABLE
Sleeping Thread-0
thread state(after 1sec)=TIMED_WAITING
Stopping Thread-0
thread state(after join)=TERMINATED
상태 확인 및 주요 메소드
리턴 타입 | 메소드 이름 및 매개 변수 | 설명 |
void | checkAccess() | 현재 수행중인 스레드가 해당스레드를 수정할 수 있는 권한이 있는지 확인하며 권한이 없다면 SecurityException발생 |
void | isAlive() | 스레드가 살아있는지 확인. 해당 스레드의 run()메소드가 종료되었는지 아닌지 확인 |
void | isInterrupted() | run() 메소드가 정상적으로 종료되지 않고 interrupt()메소드의 호출을 통해서 종료되었는지 확인 |
static boolean | interrupted() | 현재 스레드가 중지되었는지 확인 |
static int | activeCount() | 현재 스레드가 속한 스레드 그룹의 스레드 중 살아있는 스레드 개수 리턴 |
static Thread | currentThread() | 현재 수행중인 스레드의 객체를 리턴 |
static void | dumpStack() | 콘솔 창에 현재 스레드 스택정보 리턴 |
Object 클래스에 선언된 스레드와 관련된 메소드
리턴 타입 | 메소드 이름 및 매개 변수 | 설명 |
void | wait(), wait(long timeout), wait(long timeout, int nanos) |
다른 스레드가 Object객체에 대한 notify() 메소드나 notifyAll() 메소드를 호출할 때까지 현재 스레드가 대기 - 매개변수가 있는경우 지정한 시간만큼 |
void | notify() | Object객체의 모니터에 대기하고 있는 단일 스레드를 깨운다 |
void | notifyAll() | Object객체의 모니터에 대기하고 있는 모든 스레드를 깨운다 |
ThreadGroup에서 제공하는 메소드
ThreadGroup은 스레드 관리를 용이하게 하기 위한 클래스
리턴 타입 | 메소드 이름 및 매개 변수 | 설명 |
int | activeCount() | 실행중인 스레드 개수 리턴 |
int | activeGroupCount() | 실행중인 스레드 그룹의 개수 리턴 |
int | enumerate(Thread[] list), enumerate(Thread[] list, boolean recurse) |
현재 스레드 그룹에 있는 모든 스레드를 매개 변수로 넘어온 스레드 배열에 담는다 |
int | enumerate(ThreadGroup[] list), enumerate(ThreadGroup[] list, boolean recurse) |
현재 스레드 그룹에 있는 모든 스레드를 매개 변수로 넘어온 스레드 그룹 배열에 담는다 |
String | getName() | 스레드 그룹의 이름을 리턴 |
ThreadGroup | getParent() | 부모 스레드 그룹 리턴 |
void | list() | 스레드 그룹의 상세정보 출력 |
void | setDaemon(boolean daemon) | 지금 스레드 그룹에 속한 스레드들을 데몬으로 지정 |
'JAVA > about java' 카테고리의 다른 글
[Polymorphism] 다형성과 오버라이딩(overriding) vs 오버로딩(overloading) (0) | 2023.08.18 |
---|---|
[Class Loader] 클래스 로딩 시점 / 클래스 로딩 / 클래스 초기화 (0) | 2023.07.02 |
[static] satic 블록, static 변수와 static 메서드 (0) | 2023.05.03 |
[Collection] Map (0) | 2023.05.01 |
[Collection] Set, Queue (0) | 2023.04.30 |