즐겁게!! 자신있게!! 살아보세!!

재밌는 인생을 위하여! 영촤!

Language_Study/JAVA

[JAVA, App] 15.MutualExclusion

Godwony 2020. 12. 27. 12:01
728x90
반응형

Mutual Exclusion

  • 하나의 스레드가 수정 중인 공유자원을 사용이 끝나기 전에 다른 스레드가 수정하면 안된다.

1.volatile

  • 변수 앞에 붙이는 예약어로 변수가 연산에 사용될 때 원자성을 보장
    • 원자성: 작업이 완전히 끝나기 전에는 다른 작업이 끼어들 수 없다.
  • 32bit 시스템에서 64bit 자료형인 long, double 을 멀티 스레드 환경에서 사용하다 보면 작업이 완료되기 전에 다른 스레드에서 사용해서 잘못된 결과를 사용할 수 있습니다.
    • 이 경우에 volatile을 붙여주면 위와 같은 상황이 발생하지 않습니다.

2.synchronized 메소드

  • 메소드 결과형 앞에 synchronized를 붙이면 메소드를 동기화해서 실행
  • 이 메소드가 완료되기 전에는 다른 스레드가 수행될 수 없도록 합니다.

3.synchronized 블럭

synchronized(인스턴스){
    코드
}
  • 블럭 내에서 인스턴스를 사용하는 부분만 동기화가 됩니다.
    • 한 번에 이루어져야 하는 부분만 동기화 할 수 있습니다.

생산자와 소비자 문제

  • 공유 자원을 생성하는 스레드와 사용하는 스레드가 동시에 동작 중일 때 소비자 스레드는 생산자 스레드가 공유 자원을 생성을 해주었을 때 동작을 해야 합니다.
    • 공유 자원을 생성하지 않은 상태에서 소비자 스레드가 동작하게 되면 예외가 발생합니다.

1.wait 메소드

  • 스레드의 작업을 대기 시키는 메소드

    • wait(): notify()가 호출될 때 까지 대기
    • wait(long msec): 매개변수로 대입된 시간만큼 대기

2.notify 메소드

  • 대기 중인 스레드에게 신호를 보내서 작업을 수행하도록 해주는 메소드
  • notify(): wait 중인 스레드 중 1개에게만 신호를 보내는 메소드
  • notifyAll(): wait 중인 모든 스레드에게 신호를 보내는 메소드

3.주의할 점

  • wait 와 notify 메소드는 Object 클래스의 메소드
  • wait 와 notify는 synchronized 메소드에서만 동작
    • 메소드가 synchronized 메소드가 아니면 예외가 발생합니다.

4.실습

1) 공유 자원 클래스

  • List를 인스턴스로 변수로 갖고 List에서 데이터 1개를 꺼내서 출력하는 메소드 와 List에 데이터 1개를 저장하는 메소드를 소유
//공유 자원을 소유할 클래스
public class Product {
    //공유자원 변수
    private List<Character> list;

    //생성자 - list를 초기화
    public Product() {
        list = new ArrayList<Character>();
    }

    //데이터 1개를 받아서 저장하는 메소드
    public void put(Character ch) {
        list.add(ch);
        System.out.println(ch + "가 입고 되었습니다.");
        try {
            Thread.sleep(1000);
        }catch(Exception e) {}
        System.out.println("입고 후 현재 수량:" + list.size());
    }

    //데이터 1개를 꺼내서 출력하는 메소드
    public void get() {
        //첫번째 데이터를 삭제하고 리턴
        Character ch = list.remove(0);
        System.out.println(ch + "가 출고 되었습니다.");
        try {
            Thread.sleep(1000);
        }catch(Exception e) {}
        System.out.println("출고 후 현재 수량:" + list.size());
    }
}

2) 생산자 스레드

  • Product를 생성자의 매개변수로 받아서 스레드를 이용해서 26번 데이터를 삽입
  • Thread 클래스로부터 상속받는 Producer 클래스
public class Producer extends Thread {
    private Product product;

    //외부에서 인스턴스 주입받아서 객체를 생성하는 생성자
    public Producer(Product product) {
        this.product = product;
    }

    //스레드로 동작하는 메소드
    public void run() {
        for(char i = 'A'; i <= 'Z'; i++) {
            product.put(i);
        }
    }
}

3) 소비자 스레드

  • Product를 외부에서 받아서 스레드로 26번 get 동작
  • Thread 클래스로부터 상속받는 Customer
public class Customer extends Thread {
    //공유 자원 변수
    private Product product;

    //외부에서 주입받는 생성자
    public Customer(Product product) {
        this.product = product;
    }

    public void run() {
        for(int i=0; i<26; i=i+1) {
            product.get();
        }
    }
}

4) main 메소드를 소유한 Main 클래스

public class Main {

    public static void main(String[] args) {
        //공유 자원 생성
        Product product = new Product();

        //Thread 클래스를 상속받은 클래스의 인스턴스를 만들고 스레드로 동작
        Customer customer = new Customer(product);
        Producer producer = new Producer(product);

        customer.start();
        producer.start();
    }
}

5) 이 상태에서 실행하면 customer 스레드가 예외를 발생시킵니다.

  • product에 데이터가 없는데 읽을려고 해서 예외 발생
  • 특정 조건을 만족하지 않으면 wait를 호출해서 대기모드로 만들고 조건이 만족되면 notify를 호출해서 wait 중인 스레드를 깨워주면 됩니다.

6) Product 클래스의 메소드를 수정

    //데이터 1개를 받아서 저장하는 메소드
    //synchronize 가 붙지 않으면 jvm이 모니터링을 하지 않기 때문에 신호를 보낼 수 없습니다.
    public synchronized void put(Character ch) {
        //2개가 넘으면 대기
        if(list.size() > 2) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        list.add(ch);
        //데이터 추가 후 신호를 보냅니다.
        notify();

        System.out.println(ch + "가 입고 되었습니다.");
        try {
            Thread.sleep(1000);
        }catch(Exception e) {}
        System.out.println("입고 후 현재 수량:" + list.size());
    }
    //데이터 1개를 꺼내서 출력하는 메소드
    public synchronized void get() {
        //list에 데이터가 없으면 대기
        if(list.size() == 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //첫번째 데이터를 삭제하고 리턴
        Character ch = list.remove(0);
        notify();
        System.out.println(ch + "가 출고 되었습니다.");
        try {
            Thread.sleep(1000);
        }catch(Exception e) {}
        System.out.println("출고 후 현재 수량:" + list.size());
    }

Semaphore

  • 공유자원을 동시에 사용할 수 있는 스레드의 개수를 설정할 수 있는 클래스
  • 이 클래스를 이용하면 동시에 수행되는 스레드의 개수를 지정 가능

    1.생성자

  • Semaphore(int permit): 동시에 수행될 스레드 개수

2.메소드

  • acquire(): 공유자원에 Lock을 설정하는 메소드, 이 메소드가 호출되면 공유자원 개수가 1개 줄어듭니다.

  • release(): 공유자원에 Lock을 해제하는 메소드, 이 메소드가 호출되면 공유자원 개수가 1개 늘어납니다.

3.작업 방법

  • Semaphore 클래스의 인스턴스를 스레드 외부에서 생성
  • Semaphore 인스턴스를 스레드에게 전달해서 공유자원을 사용하는 부분에서 이용

1) 세마포어가 적용된 스레드 클래스

public class ThreadEx extends Thread {
    //세마포어 변수
    private Semaphore sem;

    //외부에서 Semaphore를 주입받습니다.
    public ThreadEx(Semaphore sem) {
        this.sem = sem;
    }

    public void run() {
        try {
            //lock을 취득 - 사용 개수가 1개 줄어듬
            sem.acquire();
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }

        System.out.println("몬스터 생성");
        try {
            Thread.sleep(3000);
        }catch(Exception e) {}

        System.out.println("몬스터 소멸");
        //lock을 해제 - 사용 개수가 1개 늘어남
        sem.release();
    }
}

2) main 메소드

public class SemaphoreMain {

    public static void main(String[] args) {
        //동시에 3개 까지 실행되는 세마포어 생성
        Semaphore sem = new Semaphore(3);
        for(int i=0; i<20; i=i+1) {
            ThreadEx th = new ThreadEx(sem);
            th.start();
        }
    }
}

병렬 처리

  • 동시에 2개 이상의 작업을 수행하는 것
  • 자바에서는 fork&join 프레임워크(1.7) 와 스트림 API(1.8)에서 제공
    • 1부터 100까지의 합계를 구하는 경우 프로세서가 2개 이상이라면 1-50까지를 하나의 프로세서가 계산하고 다른 프로세서가 51-100까지의 합을 계산해서 더하면 훨씬 효율적으로 작업을 할 수 있게 됩니다.
  • 작성 방법
    • 작업을 수행하고 리턴 여부에 따라서 RecursiveAction(리턴 값이 없는 경우) 이나 RecursiveTask(리턴 값이 있는 경우) 클래스를 상속받는 클래스를 생성하고 compute 메소드를 재정의해서 작업을 어떻게 분할해서 수행할 지 작성하고 결과를 리턴하면 됩니다.
    • RecursiveTask는 리턴할 데이터 타입으로 제너릭을 적용해야 하고 compute의 리턴 타입도 일치해야 합니다.
728x90
반응형

'Language_Study > JAVA' 카테고리의 다른 글

[JAVA, App] 17.자바GUI  (0) 2020.12.27
[JAVA, App] 16.이벤트처리  (0) 2020.12.27
[JAVA, App] 14.기본API클래스  (0) 2020.12.27
[JAVA, App] 13.Framework  (0) 2020.12.25
[JAVA, App] 12.Interface  (0) 2020.12.25