자바의 정석

자바의 정석 13장

그zi운아이 2023. 8. 28. 12:45

이 글은 Java의 정석 3rd Edition을 읽고 정리한 내용입니다.

Chapter 13

 

지네릭스,열거형,애너테이션


1.프로세스와 쓰레드 

프로세스란 실행중인 프로그램을 뜻하며 보조기억 장치에 적재되어있는 프로그램이 메모리에 적재되어 cpu를 통해 실행되고 있는 상태이다.

쓰레드는 프로세스의 자원을 이용해서 실제로 작업을 수행하는 것이다 모든 프로세스는 최소 하나의 쓰레드가 존재하며 둘이상의 쓰레드를 가진 프로세스를 멀티쓰레드 라고한다.

 

멀티태스킹과 멀티 스레딩 

우리가 사용하는 윈도우나 유닉스등 대부분의 OS 는 멀티테스킹을 지원하여 여러개의 프로세스가 동시에 실행될 수 있다.

이는 이와 마찬가지로 멀티쓰레딩은 하나의 프로세스 내에서 여러 쓰레드가 동시에 작업을 수행하는 것이다.

하나의 코어는 한개의 작업만을 할 수 있다. 실제로 동시에 처리되는 작업의 개수는 코어의 개수와 일치하나 동시성을 가지고 싱글 스레드도 각 프로세스간의 문맥교환을 통해 멀티 태스킹을 지원한다. 

 

멀티스레딩의 장단점

멀티쓰레드의 장점
- cpu의 사용류을 향상시킨다.
- 자원을 보다 효율적으로 사용할 수 있다.
- 사용자에 대한 응답성이 향상된다.
- 작업이 분리됭 ㅓ코드가 간결해진다.
멀티쓰레드 단점
같은 프로세스 내에서 자원을 공유하면서 작업을 하기 때문에 동기화 문제가 발생할수 있다.
교착상태와 같은 문제가 발생 할수 있다.

 

2.쓰레드의 구현과 실행 

쓰레드를 구현하는 방법으로 Thread클래스를 상속받는 방법과 Runnable 인터페이스를 구현하는 방법, 두 가지가 있다.

Runnable인터페이스를 구현하는 방법이 재사용성이 높고 코드의 일관겅을 유지할 수 있기에 보다 객체지향적이다.

Thread클래스 상속
Class MyThread extends Thread {
	public void run { /* 작업내용 */}
 }
Runnable 인터페이스 구현 
Class MyThread implements Runnable { 
	public void run { /* 작업 내용 */ }
}

 

쓰레드의 실행 

쓰레드를 생성했다고 자동의로 실행되는 것이 아니라 start() 메소드를 호출해야만 쓰레드가 실행된다.

또한 start가 호출되었다고 바로 실행이 되는것이 아니라 준비 큐에 들어가게 되고 자신의 차례가 오면 실행이 된다.

 

3. start() 와 run()

 start() 메서드와 run() 메서드의 차이는 call stack 을 생성해 하는지 하지 않는 점이 있다 start() 는 call stack 을 생성한 후 run() 메소드를 실행하는 로직이고 run 메소드는 현재 call stack에 담겨 실행되는 로직이다. 멀티쓰레드 환경에선 start()를 싱글 스레드 상태에선 run을 사용하면 될꺼 같다.

main 쓰레드 

main 메서드의 작업을 수행하는 것도 쓰레드 이며 이를 main 쓰레드라고 한다.

 

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

두개의 작업을 하나의 쓰레드로 처리하는 경우에는 한 작업이 종료된후 다른 작업을 시작해야 되지만, 두개의 쓰레드로 하는 경우는 짧은 시간동안 두개를 번갈아 가면서 수행하여 동시에 처리되는 것 같이 느끼게 한다

싱글쓰레드와 멀티쓰레드의 비교

5. 쓰레드의 우선순위 

쓰레드의 우선순위는 TCB(쓰레드 제어 블록) 에 저장되어 있다. 이 우선순위 에 따라 쓰레드가 얻는 실행시간이 달라지고 작업의 중요도에 따라 우선순위를 다르게 지정하여 특정 쓰레드가 더 많은 작업시간을 갖도록 할 수 있다.

 

쓰레드 우선순위 지정

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 // 보통우선순위

6. 쓰레드 그룹

쓰레드 그룹은 서로 관련된 쓰레드를 그룹으로 다루기 위한 것으로, 폴더를 생성해 관련된 파일을 함께 관리하는 것처럼 쓰레드를 그룹으로 묶어서 관리할 수 있다.

쓰레드 그룹은 보안상의 이유로 도입된 개념이다.

7. 데몬 쓰레드 

데몬 쓰레드는 데몬 쓰레드가 아닌 쓰레드의 작업을 돕는 보저역할을 수행하는 쓰레드로 가비지 컬렉터 워드프로세서의 자동저장 등이 잇다.

데몬 쓰레드는 무한루프와 조건문을 이용해서 실행 수 대기하고 있다가 특정 조건이 만족 되면 작업을 수행하고 다시 대기하도록 장성한다.

 

8. 쓰레드의 실행 제어 

쓰레드의 프로그래밍이 어려움이유는 동기화와 스케줄링 때문이다.

 

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

쓰레드의 상태

 9. 쓰레드의 동기화

씽글 쓰레드의 경우 공유 자원이 없기 때문에 작업하는데 문제가 없지만 멀티 쓰레드의 경우 여러개의 쓰레드가 같은 자원은 공유하기에 작업이 마쳤을때 의도했던 것과는 다르게 결과를 얻을 수 있다.

 

다른 쓰레드에 의해 방해받지 않도록 하는 것이 필요한데 임계 구역과 잠금이 있다.

 자바는 모니터를기반으로 동기화를 지원하고 과거엔 Syschronized블럭을 이용한 동기화문제를 해결했지만 JDK1.5부터는 다양한 패키지를 통해 동기화를 구현할 수 있도록 지원한다.

 

9.1 synchronized를 이용한 동기화

메소드 전체를 임계영역으로 지정하는 방식과 특정 블럭을 임계영역으로 지정시켜 동기화를 시키는 방법이 있다 

1. 메서드 전체를 임계영역으로 지정
public synchronized void calcSum(){
	
}

2.특정 영역을 임계 영역으로 지정 
synchronized(객체의 참조 변수){

}

synchronized 영역에 접근하면 lock을 획득하게 되고 이영역을 벗어나면 lock을 반납하여 동기화 구현한는 방법이다.

 

9.2 wait와 notify()

synchronized동기화는 특정 스레드가 락을 가진 상태로 오랜 기간을 보낼 수 있다는 단점을 가지고 있다.

이러한 상황을 개선하기 위해 고안된 것이 바로 wait()와 notify()이다.

wiat()이 호출되면 실행중이던 쓰레드는 대기 상태로 통지를 기다리고 notify가 호출되면 해당 대기실에 쓰레드 중에 임으의 쓰레드만 통지를 받는다. notifyAll은 기다리고 있는 모든 쓰레드에게 통보하지만 lock을 얻을 수 있는 것은 하나의 쓰레드 이고 나머지는 다시 lock을 기다리고 있다.

 

9.3 Lock과 Condition을 이용한 동기화 

같음 메소드 내에서만 lock을 걸수 있다는 제약이 불편하기 떄문에 lock클래스를 사용할 수 있다.

ReetrantLock 	재진입이 가능한 lock, 가장 일반적인 배타 lcok
ReentrantReadWriteLock 	읽기에는 공유적이고, 쓰기에는 배타적인 lock
StampedLock 	ReentrantReadWriteLcok에 낙관적인 lock의 기능을 추가

 

 

9.4 volatile

도중에 메모리에 자장된 변수의 값이 변경되었는데도 캐시에 저장된 값이 갱신되지 않아서 메모리에 저장된 값이 다른 경우가 발생하는데 변수에 volatil을 붙이면 코어가 변수의 값을 읽어올 때 캐시가 아닌 메모리에서 읽어오기 때문에 캐시와 메모리 간의 값의 불일치가 해결된다.

synchronized블럭을 이용하면 캐시와 메모리간의 동기화가 이뤄지기 때문에 값의 불일치를 해소할 수 있지만 

더 간단한 방법으론 변수를 선얼할 때 volatile을 붙이는 것이다.

volatile boolean suspended = false;
volatile boolean stopped = false;

 

9.5. fork & join 프레임 웍 

fork & join 프레임 욱은 하나의 작업을 작은 단위로 나눠서 여러 쓰레드가 동시에 처리하는 것을 쉽게 만들어 준다.

RecursiveAction 반환값이 없는 작업을 구현할 때 사용
RecursiveTask 반환값이 있는 작업을 구현할 때 사용

두 클래스 모두 comput()라는 추상 메서드를 가지고 있는데, 상속을 통해 이 추상 메서드를 구현해야 된다.

public abstract class RecursiveAction extends ForkJoinTask<Void> {
	protected abstract void compute();
}

public abstract class RecursiveTask<V> extends ForkJoinTask<V> { 
	V result; 
    protected abstract V compute();
}

compute()의 구현 

 

compute()를 구현할 때는 수행할 작업 외에도 작업을 어떻게 나눌 것인가에 대해서도 알려줘야 된다.

 

fork()와 join()

fork() 해당 작업을 쓰레드 풀의 작업 큐에 넣는다 . 비동기 메서드 
join() 해당 작업의 수행이 끝날 때까지 기다렸다가, 수행이 끝나면 그 결과를 반환하다. 동기 메서드

 

'자바의 정석' 카테고리의 다른 글

자바의 정석 16  (0) 2023.08.31
자바의 정석 12장  (0) 2023.08.27
자바의 정석 11장  (0) 2023.08.22
자바의 정석 10장  (0) 2023.08.21
자바의 정석 6장  (0) 2023.06.13