2018년 3월 28일 수요일

Thead사용법과 notify(), notifyAll(), wait() 사용시 주의점

1. Thread 구현 방법

자신이 Runnable 객체를 가지고 있으면 Runnable의 run()메소드를 호출하고 아니면 다른경우의 run()을 호출한다.

1.1. 상속

public class ThreadExam01 {
    public static void main(String[] args) {
        MyThread01 t1 = new MyThread01("*");
        MyThread01 t2 = new MyThread01("-");
        MyThread01 t3 = new MyThread01("|");
        t1.start();
        t2.start();
        t3.start();
        System.out.println("end!!");

    }
}
/*
Thread를 만드는 방법.
1) Thread를 상속받는다.
2) run() 메소드를 오버라이딩 한다.
   run() 메소드안에 동시에 수행하고 싶은 작업(코드)를 작성한다.
3) Thread의 실행은 Thread인스턴스를 생성 후 start()메소드를 사용한다.
*/
class MyThread01 extends Thread{
    private String name;
    public MyThread01(String name){
        this.name = name;
    }

    @Override
    public void run(){
        for(int i=0; i<=10; i++){
            System.out.print(name);
            try{ // 0<= x < 1000
                Thread.sleep((int) (Math.random() * 1000));
            }catch (Exception ex){

            }
        }
    }
}

1.2. 인터페이스

public class ThreadExam02 { //템플릿 메소드 패턴
    public static void main(String[] args) {
        /*
            2) Thread(Runnable) 생성자에 Runnale인터페이스를 구현하고 있는 인스턴스를 파라미터로 전달한다.
        */
        Thread t1 = new Thread(new MyThread02("*"));
        Thread t2 = new Thread(new MyThread02("-"));
        Thread t3 = new Thread(new MyThread02("|"));
        t1.start();
        t2.start();
        t3.start();
        System.out.println("end!!");
    }
}

/*
Thread를 만드는 방법.
1) Runnable 인터페이스를 구현한다.
2) run() 메소드를 구현한다.
   run() 메소드안에 동시에 수행하고 싶은 작업(코드)를 작성한다.
3) Thread의 실행은 Thread인스턴스를 생성 후 start()메소드를 사용한다.
*/
class MyThread02 implements Runnable {
    private String name;
    public MyThread02(String name){
        this.name = name;
    }

    @Override
    public void run(){
        for(int i=0; i<=10; i++){
            System.out.print(name);
            try{ // 0<= x < 1000
                Thread.sleep((int) (Math.random() * 1000));
            }catch (Exception ex){

            }
        }
    }
}

2. 동기화(synchronized)

하나의 객채에서 멀티스레드 환경일때 하나의 스레드만 사용할수 있게 동기화 처리하는것이다.
하나의 클래스에 synchronized 가 3개의 메소드가 있고 2개의 메소드에만 synchronized가 적용되어있다고 하면 synchronized가 있는 메소드들만 동기화 처리되고 하나의 메소드는 스레드가 공유하여 실행된다.
synchronized가 처리된 메소드들은 하나의 묶음으로 되어있다고 생각하며 좋을거 같다. 스레드가 synchronized가 처리된 메소드중에서 하나를 호출하면 그 쓰레드는 Lock을 획득하고 다른 synchronized 처리가 된 메소드는 대기하게 된다.

package examples;


public class ThreadExam04 {
    public static void main(String[] args) {
        Exam exam = new Exam();
        MyThread04 t1 = new MyThread04(exam, 1);
        MyThread04 t2 = new MyThread04(exam, 2);
        MyThread04 t3 = new MyThread04(exam, 3);

        t1.start();
        t2.start();
        t3.start();
    }


}

class Exam{

    public void a() {
        for(int i=0; i<10; i++){
            System.out.print("a");
            try{ // 0<= x < 1000
                Thread.sleep((int) (Math.random() * 1000));
            }catch (Exception ex){

            }
        }
    }

    public synchronized void b() {
        for(int i=0; i<10; i++){
            System.out.print("b");
            try{ // 0<= x < 1000
                Thread.sleep((int) (Math.random() * 1000));
            }catch (Exception ex){

            }
        }
    }

    public synchronized void c() {
        for(int i=0; i<10; i++){
            System.out.print("c");
            try{ // 0<= x < 1000
                Thread.sleep((int) (Math.random() * 1000));
            }catch (Exception ex){

            }
        }
    }
}

class MyThread04 extends Thread{
    private Exam exam;
    private int num;
    public MyThread04(Exam exam, int num){
        this.exam = exam;
        this.num = num;
    }

    @Override
    public void run(){
        if(num == 1){
            exam.a();
        }else if(num == 2){
            exam.b();
        }else if(num == 3){
            exam.c();
        }

    }


}

2.1 wait(), notify() ,notifyAll() 함수

Lock을 가지고 있는 쓰레드가 실행중 wait() 함수를 호출하면 현재 가지고 있는 Lock을 반환하고 대기 상태로 빠지며 다른 쓰레드가 notifyAll() 함수를 호출하면 대기 상태에 있는 쓰레드를 깨운다. 대기 상태에 있던 다수의 쓰레드가 깨어나면서 다시 경쟁을 하며  하나의 쓰레드가 Lock을 획득한다.
notify()라는 함수는 하나의 쓰레드를 깨우는 것인데 대기 상태에 들어간 순서대로 깨우는것이 아니니 사용시 주의해야한다.
wait(), notifyAll() 함수를 사용할때 실수할 수 있는 경우를 알아보자.

public class ThreadBank{
    /*
  *  withdraw 함수와 deposit 함수를  synchronized 키워드를 사용 하였다.
  *  한 쓰레드가 withdraw 함수를 실행하는 동안 다른 쓰레드가 deposit 함수를 호출하거나
  *  withdraw 함수를 호출할 수 없다.
  */

    private int money = 0;

    ThreadBank(int money) {
        this.money = money;
    }

    synchronized void withdraw(int value) {

        try {
            // if 문을 사용하여 현재 money 가 0 인지 확인한다.
            if(money == 0)
                wait(); // money 가 0이면 Lock 을 릴리즈하고 대기 상태에 빠진다.

            money -= value;

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Withdraw. money : " + money);

    }

    synchronized void deposit(int value) {
        money += value;

        // wait 을 호출하여 대기 상태에 빠져 있는 모든 쓰레드들을 깨운다.
        notifyAll();

        System.out.println("Deposit. money : " + money);
    }
}

class Producer implements Runnable {

    private ThreadBank mBalance;

    Producer(ThreadBank balance) {
        mBalance = balance;
    }

    @Override
    public void run() {

        // 100번 루프를 돌면서 100씩 저금한다.
        for(int i=0; i<100; i++) {
            mBalance.deposit(100);
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

class Consumer implements Runnable {

    private ThreadBank mBalance;

    public Consumer(ThreadBank balance) {
        mBalance = balance;
    }

    @Override
    public void run() {

        while(true) {
            // 무한 루프를 돌면서 10씩 인출한다.
            mBalance.withdraw(10);

        }
    }
}

class Main {

    public static void main(String[] args) {

        ThreadBank bank = new ThreadBank(0);

        Producer producer = new Producer(bank);
        Consumer consumer = new Consumer(bank);

        // Producer 쓰레드는 1개, Consumer 쓰레드는 10개를 만들어서 시작시킨다.
        new Thread(producer).start();

        for(int i=0;i<10;i++) {
            new Thread(consumer).start();
        }
    }
}


위의 소스를 실행해 보면 정상적으로 실행될것 처럼 보이나 위의 그림과 같이 0 이하의 값으로 찍히고 있다. 왜그럴까?  wait상태에 있던 쓰레드가  LOCK을 획득하면 wait()함수 다음부터 실행되기 때문이다.
if문 처리되어있던 부분을 while로 변경하여 다시 한번 체크하게 하면 된다.

public class ThreadBank{
    /*
  *  withdraw 함수와 deposit 함수를  synchronized 키워드를 사용 하였다.
  *  한 쓰레드가 withdraw 함수를 실행하는 동안 다른 쓰레드가 deposit 함수를 호출하거나
  *  withdraw 함수를 호출할 수 없다.
  */

    private int money = 0;

    ThreadBank(int money) {
        this.money = money;
    }

    synchronized void withdraw(int value) {

        try {
            // if 문을 사용하여 현재 money 가 0 인지 확인한다.
            while(money == 0)
                wait(); // money 가 0이면 Lock 을 릴리즈하고 대기 상태에 빠진다.

            money -= value;

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Withdraw. money : " + money);

    }

    synchronized void deposit(int value) {
        money += value;

        // wait 을 호출하여 대기 상태에 빠져 있는 모든 쓰레드들을 깨운다.
        notifyAll();

        System.out.println("Deposit. money : " + money);
    }
}

Share:

0 개의 댓글:

댓글 쓰기