티스토리 뷰

Java & Kotlin

Java Thread

필자A 2022. 10. 30. 15:54

 

 

 

 

 

 

 

💬 Thread 클래스와 Runnable 인터페이스의 차이

 

Runnable은 추상 메서드인 run() 하나만을 가진 functionalInterface이며

단독으로는 아무 기능이 없습니다.

 

Thread는 Runnable의 구현체입니다.

스레드를 위한 실직적인 기능은 해당 클래스에 정의되어있습니다.

 

 

 


💬 Thread의 상태

 

자세한 내용은 링크 참고 https://dogg.tistory.com/203

 

 

 

 

 


💬 Thread의 우선순위

 

 

OS의 스케쥴링 정책에 상이하며 때에 따라서 다릅니다.

thread1 -> thread2 -> thread3 순서대로 처음에 실행이 종료되었다면

두 번째에는 thread2 -> thread3 -> thread1 순서로 종료될 수 있습니다.

 

Java 수준에서 우선순위를 지원해줄 수 있으나 절대적이지 않다.

 

 


💬 Main Thread

 

Java로 작성된 프로그램을 실행될 때는 처음에 main() 메서드를 처음으로 시작합니다.

그리고 스레드를 추가적으로 작성하여 실행할 수도 있다.

기본적으로는 일반쓰레드로서 작동한다.

 

데몬 스레드 설정을 함으로써 주스 레드에 종속적인 스레드로 설정도 가능하다.

(주 스레드가 종료되면 함께 종료, 생명주기를 함께 함)

 

 


💬 동기화

 

 

임계구역안에서 스레드가 동시에 코드를 실행하는 것을 막는 것

 

동기화 방법의 3가지

 

1. synchronized

2. atomic integer

3. volatile

 

 

 

 

 

 

 

 

 

1. Synchronized

 

📗사용방법 

 

💬1. instance method

 

public class MyAddClass
{
    private int count = 0;
    public synchronized void add(int value){
        this.count += value;
    }
}

메서드가 임계 구역으로 설정됨을 의미

동기화된 인스턴스의 메서드는 instance 단위에서 동기화됩니다.

 

동일한 인스턴스에서는 임계 구역에 1개의 스레드만 들어가는 것이 보장됩니다.

 

 

 

 

 

💬2.static  synchronized method

 

public class ThreadTestClass
{

    public static synchronized void run(String name) {
        System.out.println(name + "lock");
        try{
            Thread.sleep(1000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(name + " unlock");
    }
}
public class Main {

    public static void main(String[] args)
    {
        ThreadTestClass threadTestClass1 = new ThreadTestClass();
        ThreadTestClass threadTestClass2 = new ThreadTestClass();
        Thread thread = new Thread(() -> {
            threadTestClass1.run("첫번째");
        });
        Thread thread2 = new Thread(() -> {
            threadTestClass2.run("두번째");
        });

        thread.start();
        thread2.start();

    }
}

결과

---

첫 번째 lock
첫 번째 unlock
두 번째 lock
두 번째 unlock

---

 

instance method방식에서처럼 instracne단위로 동기화 처리되던 것이 아니라

class단위에서 동기화를 처리합니다.

 

 

 

 

 

 

💬3. synchronized block

 

해당 인스턴스 block내부를 임계 구역으로 설정

 

 

1. synchronized(this) 

해당 파일 내의 모든 synchronized block에 lock이 걸린다.

public void run(String name) {
    synchronized (this) {
        System.out.println(name + "lock");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name + " unlock");
    }
}

 

2.synchronized(object)

object 점유 여부로 block을 사용할 수 있습니다.

혹은 synchronized( block을 포함하는 class type)을 사용하여도 같은 효과가 있습니다.

public class SyncTest {
    private static final Object OBJECT = new Object();

    public void run(String name) {
        synchronized (OBJECT) {
            System.out.println(name + "lock");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + " unlock");
        }
    }

}

 

 

 

SyncTest syncTest = new SyncTest();
SyncTest syncTest2 = new SyncTest();
Thread th1 = new Thread(() -> { syncTest.run("첫번째"); });
Thread th2 = new Thread(() -> { syncTest2.run("두번째"); });
th1.start();
th2.start();

 

 

this 방식을 사용하면 인스턴스 자기 자신의 점유 여부를 기준으로 하므로

동기화가 되지 않습니다.

 

static방식을 사용하면 정적 자원을 기준으로 하므로

인스턴스가 달라도 동기화가 보장됩니다.

 

 

 

 

 

💬4. 정적 메서드 안의 블록단위

 

정적 메서드 내부에 포함되는 synchronized block의 경우

static 자원보다 늦게 초기화되는 자원을 참조가 불가능합니다.

그러므로 메타정보나 static자원만 block 인자로 넣을 수 있습니다.

 

public class ThreadTestClass4 {
    private static final Object OBJECT = new Object();

    public static void run(String name) throws InterruptedException
    {
        synchronized (OBJECT){
            System.out.println(name + " lock");
            Thread.sleep(1000);
            System.out.println(name + " unlock");
        }
    }

}

 

 

 

동기화는 보장될 수 있다. 순서도 보장할 수 있을까?

 

Thread[] threads = new Thread[4];

/** 1. thread list에 구현체 할당 */
for(int i = 0; i < threads.length; i++) {
    int finalI = i;
    threads[i] = new Thread(() -> {
        try {
            new ThreadTestClass4().run((finalI +1)+"번째");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    });
}

/** 2. 리스트 순회하며 기능 실행 */
for(Thread thread: threads) {
    thread.start();
}

결과

 

3번째 lock
3번째 unlock
4번째 lock
4번째 unlock
1번째 lock
1번째 unlock
2번째 lock
2번째 unlock

 

 

 

 

 

 

 

 

 

 

2. Atomic Integer

1. HW를 통해 연산의 원자성을 보장받는 Integer

 

2. CAS(Compare And Set) 알고리즘을 사용

* 내가 값을 변경하기 전, 기존에 있던 값이 내가 예상한 값이었을 때만 변경

 

3. synchronized의 blocking방식의 성능이슈를 해결

 

public class Test
{
    private static int cnt = 0;
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger();

        new Thread(
                new Runnable() {
                    @Override
                    public void run() {

                        for(int i = 0; i < 1000; i ++){
                            atomicInteger.incrementAndGet();
                            cnt++;
                        }
                    }
                }).start();
        new Thread(
                new Runnable() {
                    @Override
                    public void run() {

                        for(int i = 0; i < 1000; i ++){
                            atomicInteger.incrementAndGet();
                            cnt++;
                        }
                    }
                }).start();
        
        System.out.println(cnt);
        System.out.println(atomicInteger.get());
    }
}

 

결과

cnt - 359
atomic integer value - 2000

 

 

 

 

 

 

 

 

 

3. Volatile

volatile, 의미로는 휘발성 물질을 의미한다. 각설하고

해당 키워드 사용시 최적화에서 제외하며 데이터의 가시성을 제어 할 수 있다.

 

main memory에 적재된 값을 참조할 때 최적화를 위해

cpu cache에 올려두고 작업을 하며 참조된 값에 대하여 수정이 있을 경우 

flush를 하여 main memory에 적용이 필요합니다.

 

non-volatile의 경우(일반적 사용) 

thread1에서 cpu cache값을 수정후 flush가 일어나지 않습니다.

thread2는 main memory의 수정이전의 값을 참조하게 됩니다.

 

volatile의 경우 main memory의 딜레이없는 최신 값을 확인할 수 있습니다.

 

하지만

멀티스레드환경에서 사용 시 단 1개의 thread만 volatile 변수를 수정하고

나머지 thread는 read only정책을 선택해야 한다.

(연산의 원자성을 보장하지 않기 때문에 위험)

 

 

 

 

결론

 

synchronized - 연산의 원자성 보장 OK, 느림(blocking)

atomic value - 연산의 원자성 보장 OK, non-blocking

volatile - 데이터의 가시성 조절 OK, 연산의 원자성 보장 X


💬 데드락

 

process 서로가 점유한 자원을 요구하며

서로의 작업이 끝나기만을 기다리며 교착상태에 빠지는 것을 의미합니다.

 

원형큐의 형태로 이미지 자료가 많은 것 또한 특징입니다.

 

 

 

 

 

👊데드락의 발생 조건

아래의 조건중 1개라도 만족하지 않을 시 데드락은 일어나지 않습니다.

 

 

1. 상호 배제

한 번에 오직 하나의 프로세스만 해당 자원을 사용할 수 있다.

요청한 자원이 해제될 때까지 기다려야 한다.

2. 점유 대기

프로세스가 할당된 자원 가진 채로 다른 자원을 기다린다.

3. 비선점

이미 할당된 자원을 빼앗을 수 없음

4. 순환 대기

대기 프로세스의 집합이 순환 형태로 자원을 대기해야 한다.

 

 

 

 

 

👊데드락 해결법

 

💬데드락 예방하기

 

위의 조건들 중 1개를 제거하여 데드락을 예방합니다.

각 조건들을 제거하는 방식입니다.

 

1. 상호 배제

한 번에 여러 프로세스가 점유를 사용할 수 있도록 한다.

 

2. 점유 대기

프로세스 실행에 필요한 자원을 한꺼번에 요구하도록 한다.

 

3. 비선점

선점 프로토콜을 설정하여준다.(OS마다 상이하다고 한다.)

높은 우선순위의 프로세스에게 양보

 

4. 순환 대기

자원에 고유한 번호를 할당하여 프로세스의 대기순서를 단방향으로 한다.

 

 

💬교착상태 탐지, 복구

교착상태가 발생했을 경우 해당 프로세스를 종료하거나

해당 자원의 점유를 해제합니다.

 

 

💬교착상태 무시

발생하여도 해결하지 않고 무시한다.

문제를 발견하고 해결하기 위한 오버헤드보다 무시하는 게 더

이득이 큰 경우 사용

 

 

💬starvation(기아상태)과 deadlock의 미묘한 차이점

 

deadlock : n개의 process가 각각 자원을 할당한 상태에서

서로가 서로의 자원의 할당을 원할때

 

starvation : OS가 선택한 스케쥴링 기법에 의해

우선순위가 높은 프로세스가 자원을 계속 할당받아

우선순위가 낮은 프로세스가 자원 할당을 계속 못받는 상태

반응형

'Java & Kotlin' 카테고리의 다른 글

Java annotation  (0) 2022.12.10
Java enum  (0) 2022.11.02
Java Exception  (0) 2022.09.18
Java classPath  (0) 2022.09.04
Java inheritance, abstract class, interface  (0) 2022.09.03
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
글 보관함