티스토리 뷰

📌 230822 쓰레드 정리


✏️ 쓰레드 관련 용어 

Process: 프로그램이 메모리에 올라간 상태

Thread: 하나의 프로세스는 하나 이상의 thread를 가지게 되고, 실제 작업을 수행하는 단위는 thread임. 

 

Multi-threading:  여러 쓰레드가 동시에 수행되는 프로그래밍 

- 쓰레드는 각각 자신만의 작업 공간을 가진다. (Context)

- 각 쓰레드 사이에서 공유하는 자원(shared resource)이 있을 수 있다. (자바에서는 staticc instance)

- 여러 쓰레드가 자원을 공유하여 작업이 수행되는 경우 서로 자원을 차지하려는 경쟁상태(race condition) 발생할 수 있다.

- 그래서 한 쓰레드가 자원을 사용하고 있으면, 다른 쓰레드는 접근을 못하게 lock을 해줘야한다. 

 

 

✏️ 쓰레드의 여러 메소드들

1. priority 우선순위

- 쓰레드는 우선순위를 지정하여 실행시킬 수 있다. 

- 우선순위가 높은 쓰레드가 CPU 배분을 받을 확률이 높다.

- 디폴트로는 우선순위 5가 부여된다. (MIN_PRIORITY: 1 MAX_PRIORITY : 10)

- setPriority() / getPriority() 이용하여 사용한다.

 

2. join()

- 동시에 두개 이상의 Thread가 실행될 때 다른 Thread의 결과를 참조하여 실행해야하는 경우 join() 함수를 사용한다.

- join() 함수를 호출한 Thread가 not-runnable 상태로 된다.

- 다른 쓰레드 수행이 끝나면 runnable 상태로 돌아온다.

- main에서 사용하면 main은 쓰레드가 수행이 종료될 때까지 멈추게 된다. 

 

3. interrupt()

- 다른 쓰레드에 예외를 발생시키는 interrupt을 보낸다. 

- 쓰레다가 join(),sleep(),wait() 함수에 의해 not-runnable 상태일 , interrupt() 메서드를 호출하면 다시 runnable 상태가 있다.

 

 

✏️ 쓰레드를 종료하고 싶을 때.

예전에는 stop() 메소드를 사용했지만 요즘에는 잘 사용하지 않는다. 

대신 flag 변수를 이용한다. 

무한 반복일 경우 while(flag)의 flag 변수 값을 false로 바꾸어 종료를 시킴.

 


✏️  멀티 쓰레드 프로그래밍에서의 동기화 문제 해결

 

Critical section(임계영역):  두 개 이상의 스레드가 공유 자원에 동시에 접근하는 경우 문제가 생길 수 있기 때문에 한 스레드만 접근이 가능한 영역이다. 즉 한 스레드가 독점할 수 있도로 보장된 코드 영역이다. 

 

Semaphore (세마포어) : 임계 영역을 해결하기 위한 방법 중 하나이며, 특별한 형태의 시스템 객체이며 get/release 두개의 기능이 있다. 

- 한 순간 오직 하나의 스레드만이 세마포어를 얻을 수 있고, 나머지 스레드들은 대기 (blocking) 상태가 된다. 

- 세마포어를 얻은 쓰레드만이 critical section 들어갈 있다. 

 

 

✏️  자바에서는 synchronized 메서드나 synchronized 블럭을 사용.

Synchronized 블럭: 현재 객체 또는 다른 객체를 lock으로 만든다. 

 

Synchronized 메소드: 객체의 메소드에 synchronized 키워드를 사용.

- 현재 이 메서드가 속해있는 객체에 lock을 건다. 

- 자바에서는 deadlock을 방지하는 기술이 제공되지 않으므로 이 메서드를 사용하면, 다른 synchronized 메소드는 호출하지 않도록 한다. 

💡 교착상태 deadlock? 둘 이상의 프로세스가 다른 프로세스가 점유하고 있는 자원을 서로 기다릴 때 무한 대기에 빠지는 상황.

 

📝  사용 예제 : 은행 

은행에서 같은 통장을 공유하고 있는 park, parkWife가 있다.

park은 3000원 저축을, parkWife 는 1000원을 출금하고자 할 때,

둘은 같은 자원을 공유하고 있으므로 동기화 처리를 해줘야한다.

만약 안 해줄 경우, 출금할 때 시간(0.2초)이 저축시간(3초)보다 빠르므로 출금이 먼저 이루어져 값이 이상해진다. 

 

class Bank { //Bank 가 shared resource
    private int money = 10000;

    //public synchronized void saveMoney(int save)  {
     public void saveMoney(int save)  {
        int m = getMoney();
        try {
            Thread.sleep(3000); //3초
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        setMoney(save+m);
    }


    public synchronized void minusMoney(int minus) {
        int m = getMoney();
        try {
            Thread.sleep(200); //0.2초
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        setMoney(m - minus);
    }

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }
}
class Park extends Thread {
    public void run() {
        synchronized (SyncMain.myBank) {
            System.out.println("start save");
            SyncMain.myBank.saveMoney(3000);
            System.out.println("save Money(3000) : " + SyncMain.myBank.getMoney());
        }
    }
}

class ParkWife extends Thread {
    public void run() {
        System.out.println("start minus");
        SyncMain.myBank.minusMoney(1000);
        System.out.println("minus Money(1000) : " + SyncMain.myBank.getMoney());
    }
}

public class SyncMain {
    public static Bank myBank = new Bank();

    public static void main(String[] args) throws InterruptedException {
        Park p = new Park();
        p.start();
        Thread.sleep(200);

        ParkWife parkWife = new ParkWife();
        parkWife.start();
    }

}

실행 결과) 동기화 처리를 안해준 경우, 이상한 값이 나온다. 

 


✏️ wait()/notify() 메서드를 활용한 동기화 프로그래밍 

  • 리소스가 어떤 조건에서 더이상 유효하지 않을 경우, 리소스를 기다리기 위해 thread가 wait 상태가 된다. 
  • wait() 상태가 된 thread는 notify()가 호출 될때 까지 기다린다. 
  • 유효한 자원이 생기면 notify()가 호출되고 wait() 하고 있는 thread 중 무작위로 하나의 thread를 재시작하도록 한다.
  • 영원히 호출되지 못한 스레드가 있을 수 있기 때문에, 이럴 경우 notifyAll()을 사용한다. 
  • notifyAll()이 호출되는 경우 wait() 하고 있는 모든 thread가 재시작된다. 
  • 이 경우 유효한 리소스인 만큼의 thread만이 수행될 수 있고 자원을 갖지 못한 thread인 경우는 다시 wait() 상태로 만든다. 
  • 자바에서는 notifyAll() 메소드의 사용을 권장한다. 

 

📝 사용 예제 : 도서관

도서관에 있는 책은 한정적이다.

만약 책이 3권이 있고 그 책을 대출하고자 하는 사람이 5명일 경우, 2명은 먼저 빌려간 3명이 반납할 때까지 기다려줘야한다. 

여기서 공유 자원이고, wait() 는 책이 다 대출해가서 0권일 때 걸어준다. 

notifyAll()는 빌려간 책이 반납했을 때, 빌리지 못한 대기중인 사람 2명에게 책을 빌릴 수 있다고 알려주는 용도이다. 즉 책을 반납하고자 할 때 notifyAll()를 명시해준다.

 

class Library { //도서관
    public ArrayList<String> shelf = new ArrayList<>();
    public Library() {
        shelf.add("태백산맥1");
        shelf.add("태백산맥2");
    }
    public synchronized String leadBook() throws InterruptedException {
        Thread t = Thread.currentThread();
//        if (shelf.size() == 0) { //도서관에 책이 없을 때. wait() X
//            return "";
//        }

        while (shelf.size() == 0) { //도서관에 책이 없을 때.
            System.out.println(t.getName() +" waiting start");
            wait();
            System.out.println(t.getName() +" waiting end");
        }

        String book = shelf.remove(0);
        System.out.println(t.getName() + ": " + book + " lend");
        return book;
    }

    public synchronized void returnBook(String book) {
        Thread t = Thread.currentThread();
        shelf.add(book);
        notifyAll();
        System.out.println(t.getName() + ": " + book + " return");
    }

}
class Student extends Thread {
    Student(String name) {
        super(name); //super -> thread
    }
    public void run() {
        try {
            String title = LibraryMain.library.leadBook();
            if (title == null) {
                System.out.println(getName() + " 빌리지 못 했음.");
            }
            sleep(5000);
            LibraryMain.library.returnBook(title);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}
public class LibraryMain {
    public static Library library = new Library();
    public static void main(String[] args) {
        Student st1 = new Student("std1");
        Student st2 = new Student("std2");
        Student st3 = new Student("std3");
        Student st4 = new Student("std4");
        Student st5 = new Student("std5");

        st1.start();
        st2.start();
        st3.start();
        st4.start();
        st5.start();

    }
}

실행 결과