본문 바로가기

개발일지/JAVA

(항해99) Java 문법 종합반 5주차 정리

<5주차>
프로세스와 쓰레드, 싱글 쓰레드와 멀티 쓰레드, 구현방법 3가지, 싱글 쓰레드와 멀티쓰레드 실습, 데몬&우선순위&쓰레드 그룹, 쓰레드 상태&sleep&interrupt, join&yield&synchronized, wait&notify, Lock&Condition, 모던자바_자바 8변경점, 모던자바_함수형 프로그래밍 실전 예제, 모던자바_stream, 모던자바_null

 

[프로세스 구조]

OS가 프로그램 실행을 위한 프로세스를 할당해줄때 프로세스안에 프로그램 Code Data 그리고 메모리 영역(Stack, Heap)을 함께 할당

1. Code Java main 메소드와 같은 코드를 말함

2. Data 는 프로그램이 실행중 저장 할 수 있는 저장공간을 의미

    1) 전역변수, 정적변수(static), 배열등 초기화된 데이터를 저장하는 공간

3. Memory (메모리 영역)

    - Stack : 지역변수, 매개변수 리턴 변수를 저장하는 공간

    - Heap : 프로그램이 동적으로 필요한 변수를 저장하는 공간 (new(), mallock())

 

[데몬 쓰레드]

보이지 않는곳(background) 에서 실행되는 낮은 우선순위를 가진 쓰레드를 말함.
    - 보조적인 역할을 담당하며 대표적인 데몬 쓰레드로는 메모리 영역을 정리해주는 가비지 컬렉터(GC)가 있음.

 

[사용자 쓰레드]

 - 보이는 곳(foregorund) 에서 실행되는 높은 우선순위를 가진 쓰레드를 말함.

 - 프로그램 기능을 담당하며 대표적인 사용자 쓰레드로는 메인 쓰레드가 있음.

 - 사용자 쓰레드 만드는법 : 기존에 만들었던 쓰레드들이 다 사용자 쓰레드 

 - JVM 은 사용자 쓰레드의 작업이 끝나면 데몬 쓰레드도 자동으로 종료시켜 버림.

 

[쓰레드 우선순위]

 - 쓰레드 우선순위는 setPriority() 메서드로 설정

 - getPriority() 로 우선순위를 반환하여 확인

 

[쓰레드 제어]

[sleep()]

 - 현재 쓰레드를 지정된 시간동안 멈추게 함

 - sleep()은 쓰레드 자기자신에 대해서만 멈추게 할 수 있음.

 - `Thread.sleep(ms);` ms(밀리초) 단위로 설정

 - 예외처리를 해야함.

 - sleep 상태에 있는 동안 `interrupt()` 를 만나면 다시 실행되기 때문에 InterruptedException이 발생할 수 있음.

 - 특정 쓰레드를 지목해서 멈추게 하는 것은 불가능

 

[interrupt()]

 - 일시정지 상태인 쓰레드를 실행대기 상태로 만듦

 - Thread 클래스 내부에 interrupted 되었는지를 체크하는 boolean 변수가 존재

 - 쓰레드가 `start()` 된 후 동작하다 `interrupt()`를 만나 실행하면 interrupted 상태가 true가 됨

 - `isInterrupted()` 메서드를 사용하여 상태값을 확인할 수 있음.

 - `sleep()` 실행 중 `interrupt()`가 실행되면 예외가 발생

 - !Thread.currentThread().isInterrupted() interrupted 상태를 체크해서 처리하면 오류를 방지할 수 있음.

 

[ join()]

 - 정해진 시간동안 지정한 쓰레드가 작업하는 것을 기다림

 - 시간을 지정하지 않았을 때는 지정한 쓰레드의 작업이 끝날 때까지 기다림

 - 시간을 지정하지 않았기 때문에 thread가 작업을 끝낼 때까지 main 쓰레드는 기다리게됨

 

[yield()]

 - 남은 시간을 다음 쓰레드에게 양보하고 쓰레드 자신은 실행대기 상태가 됩니다.

 

[synchronized]

 - 멀티 쓰레드의 경우 여러 쓰레드가 한 프로세스의 자원을 공유해서 작업하기 때문에 서로에게 영향을 줄 수 있습니다. 이로인해서 장애나 버그가 발생할 수 있음.

 - 이러한 일을 방지하기 위해 한 쓰레드가 진행중인 작업을 다른 쓰레드가 침범하지 못하도록 막는 것을 '쓰레드 동기화(Synchronization)'라고 함

 - 동기화를 하려면 다른 쓰레드의 침범을 막아야하는 코드들을 임계영역으로 설정

 - 임계영역에는 Lock을 가진 단 하나의 쓰레드만 출입이 가능

 - , 임계영역은 한번에 한 쓰레드만 사용이 가능

 - 실행할 메서드 또는 실행할 코드 묶음 앞에 synchronized 를 붙여서 임계영역을 지정하여 다른 쓰레드의 침범을 막을 수 있음. (침범을 막다. = Lock을 걸다.)

 

[wait(), notify()]

 - 침범을 막은 코드를 수행하다가 작업을 더 이상 진행할 상황이 아니면, `wait()` 을 호출하여 쓰레드가 Lock을 반납하고 기다리게 할 수 있음

 - 그럼 다른 쓰레드가 락을 얻어 해당 객체에 대한 작업을 수행 할 수 있게 되고, 추후에 작업을 진행할 수 있는 상황이 되면 `notify()`를 호출해서, 작업을 중단했던 쓰레드가 다시 Lock을 얻어 진행할 수 있게 됩니다.

    1. wait()

        실행 중이던 쓰레드는 해당 객체의 대기실(waiting pool)에서 통지를 기다립니다.

    2. notify()

        해당 객체의 대기실(waiting pool)에 있는 모든 쓰레드 중에서 임의의 쓰레드만 통지를 받습니다.

 

[Lock]

 - synchronized 블럭으로 동기화하면 자동적으로 Lock이 걸리고 풀리지만, 같은 메서드 내에서만 Lock을 걸 수 있다는 제약이 있음.

 - 이런 제약을 해결하기 위해 Lock 클래스를 사용

    1. ReentrantLock

        - 재진입 가능한 Lock, 가장 일반적인 배타 Lock

        - 특정 조건에서 Lock을 풀고, 나중에 다시 Lock을 얻어 임계영역으로 진입 가능

        - 같은 스레드가 이미 락을 가지고 있더라도 락을 유지하며 계속 실행할 수 있기 때문에 데드락이 발생하지 않음.

        - , ReentrantLock을 사용하면 코드의 유연성을 높일 수 있음.

    2. ReentrantReadWriteLock

        - 읽기를 위한 Lock과 쓰기를 위한 Lock을 따로 제공

        - 읽기에는 공유적이고, 쓰기에는 베타적인 Lock

        - 읽기 Lock이 걸려있으면 다른 쓰레드들도 읽기 Lock을 중복으로 걸고 읽기를 수행할 수 있음. (read-only)

        - 읽기 Lock이 걸려있는 상태에서 쓰기 Lock을 거는 것은 허용되지 않음. (데이터 변경 방지)

    3. StampedLock

        - ReentrantReadWriteLock에 낙관적인 Lock의 기능을 추가

        - 낙관적인 Lock : 데이터를 변경하기 전에 락을 걸지 않는 것을 말함. 낙관적인 락은 데이터 변경을 할 때 충돌이 일어날 가능성이 적은 상황에서 사용

        - 낙관적인 락을 사용하면 읽기와 쓰기 작업 모두가 빠르게 처리. 쓰기 작업이 발생했을 때 데이터가 이미 변경된 경우 다시 읽기 작업을 수행하여 새로운 값을 읽어들이고, 변경 작업을 다시 수행. 이러한 방식으로 쓰기 작업이 빈번하지 않은 경우에는 낙관적인 락을 사용하여 더 빠른 처리가 가능

        - 낙관적인 읽기 Lock은 쓰기 Lock에 의해 바로 해제 가능

        - 무조건 읽기 Lock을 걸지 않고, 쓰기와 읽기가 충돌할 때만 쓰기 후 읽기 Lock

 

[Condition]

 - wait() & notify()의 문제점인 waiting pool 내 쓰레드를 구분하지 못한다는 것을 해결한 것이 Condition

 - wait() & notify() 대신 Conditionawait() & signal() 을 사용