Thread 스레드


- C 나 C++ 의 Thread는 운영체제를 잘 알아야 하지만 Java의 Thread는 언어차원에서 해결하기 때문에 비교적 쉽다.


1. Process and Program

- 일반적으로 프로그램이라고 이야기하면 하드디스크에 저장되어 실행되지 않은 상태를 말한다. 

- 프로세스는 실행 중인 프로그램을 이야기한다.

- 프로그램 (실행X), 프로세스 (실행O)

- 프로그램이 보조기억장치에 있던 것을 cpu가 사용 할 수 있도록 

  보조기억장치에서 주기억장치로 프로그램이 로드된 상태가 프로세스다.


Multi Process (= Multi Tasking)

- 멀티프로세스는 여러개의 프로그램이 동시에 실행되는 것이다


시분할 (Time Sharing)

- 정확히 말하면 컴퓨터는 동시에 여러가지 작업을 할 수는 없다.

CPU는 한번에 한가지 작업만 할 수 있기 때문이다.

사실은 동시에 작업을 하지 않는다.

시간을 쪼개서 동시에 작업을 하는 것처럼 보이도록 한다.

이를 시분할 이라고 한다. 


Muti Processor

- 여러개의 CPU를 말한다.

- 일반적인 프로그램은 하나의 cpu만 사용하기 때문에 프로그램 성능은 똑같다.

- 윈도우와 같은 OS 는 프로그램이 멀티프로세스 

  할 수 있도록 시간사용을 스계쥴링 해주는 프로그램이다.


Thread란 무엇일까?

하나의 프로세스 안에서도 동시에 작업할 수 있어야 한다.

하나의 프로세스 안에서 실행되는 작업 단위를 스레드Thread 라고 한다.


2. Multi Thread

멀티스레드란 무엇일까?

하나의 프로세스 안에서 여러 개의 프로세스가 동시에 실행될 수 있도록 하는 것이다.

멀티스레드가 되어야 다운로드를 받는 중에도 X 버튼을 누를 수 있다.

게임을 만들 때에도 총을 쏠 때 피할 수 있으려면 멀티스레드를 할 수 있어야 한다.


3. 자바에서 지원하는 스레드 기법


1) Thread 클래스

- 다른 클래스를 상속 받을 필요가 없다면 스레드 클래스만 상속받아서 사용한다.


2) Runnamble 인터페이스

- 다른 클래스도 상속받고 스레드도 상속받아야 한다면,

자바에서는 클래스 다중상속이 안되기 때문에 다중상속이 가능한 

Runnamble 인터페이스를 상속받는다.



4. 모든 자바 클래스는 반드시 하나의 스레드를 가지고 있다.

- main 메서드가 하나의 스레드이다. 

- 모든 자바클래스는 메인스레드를 가지고 있다.


<Thread 클래스>

- 메인스레드를 제외한 나머지는 자식스레드이다.

- run() 메소드는 자식 스레드가 해야 할 일을 적어준다.




package prjThread;

class ThreadDemo extends Thread{

    //private String name;

    //어떤 스레드가 작업하는지 확인하기 위해 변수를 만든다.

    private String name;

    public ThreadDemo(String name) {

        this.name = name;

    }    

   

    

    @Override

    //callback 우리가 호출하는게 아니라 시스템이 호출하는 메서드

    //run()은 콜백메서드이기 때문에 시스템에게 부탁만 할 수 있다.

    //오버라이드 규칙상 부모로부터 받은 메서드를 똑같이 써야하기 때문에 throws 예외처리를 할 수 없다.

    public void run() {

        

        //1~5까지 합계를 구한다.

        int sum = 0 ;

        for(int i=0; i<5; i++) {

            

            try {

                sleep(100);    //thread에서 0.1초 단위로 한 쪽을 쉬게 하는 메서드이다.

            } catch (InterruptedException e) {

                // TODO Auto-generated catch block

                

                e.printStackTrace();

            }

            

            sum += i;

           

            //1~5까지 합계를 출력한다.

            //임의로 이름을 붙힐 수 있도록 만들어 준다.

            //부모클래스에게 상속할 경우 getName()을 쓴다.

            System.out.println(name + ":" + sum);

            

            //출력을 해보면 순서는 실행 할 때 마다 다르지만 하나의 작업이 끝나고 두번째 작업을 한다.

        }

    }

    

}


public class ThreadTest1 {

    

    //메인스레드를 제외한 나머지는 자식스레드이다.

    public static void main(String[] args) {

    

        //부모가 스레드이고 부모로부터 상속 받았기 때문에 인스턴스도 스레드가 된다.

        //메인스레드와 함께 스레드가 2개가 되었다.

        //0부터 5까지 합계를 구하는 스레드가 되었다.

        ThreadDemo demo1 = new ThreadDemo("first");

        

        //인스턴스가 별개로 생성됨으로 스레드가 3개가 되었다.

        //0부터 5까지 합계를 구하는 스레드가 되었다.

        //원래 스레드는 각각 다른 클래스에 해야한다.

        ThreadDemo demo2 = new ThreadDemo("second");

        

        demo1.start(); //시스템에게 호출해달라고 부탁한다.

        demo2.start(); //시스템에게 호출해달라고 부탁한다.        

    }

}


※결과값

first:0

second:0

second:1

first:1

first:3

second:3

second:6

first:6

first:10

second:10







package prjThread2;

class ThreadDemo extends Thread{

    //private String name;

    

    public ThreadDemo(String name) {

        

        super(name); //현재 스레드의 이름을 부모에게 저장시킨다.

    }

    

    @Override

    //callback 우리가 호출하는게 아니라 시스템이 호출하는 메서드

    //run()은 콜백메서드이기 때문에 시스템에게 부탁만 할 수 있다.

    //오버라이드 규칙상 부모로부터 받은 메서드를 똑같이 써야하기 때문에 throws 예외처리를 할 수 없다.

    public void run() {

        

        //1~5까지 합계를 구한다.

        int sum = 0 ;

        for(int i=0; i<5; i++) {

            

            try {

                sleep(100);    //thread에서 0.1초 단위로 한 쪽을 쉬게 하는 메서드이다.

            } catch (InterruptedException e) {

                // TODO Auto-generated catch block

                e.printStackTrace();

            }

            

            

            sum += i;

            

            //1~5까지 합계를 출력한다.

            //임의로 이름을 붙힐 수 있도록 만들어 준다.

            //부모클래스에게 상속할 경우 getName()을 쓴다.

            System.out.println(getName() + ":" + sum);

            

            //출력을 해보면 순서는 실행 할 때 마다 다르지만 하나의 작업이 끝나고 두번째 작업을 한다.

        }

    }

    

}


public class ThreadTest1 {

    

    //메인스레드를 제외한 나머지는 자식스레드이다.

    public static void main(String[] args) {

    

        

        //부모가 스레드이고 부모로부터 상속 받았기 때문에 인스턴스도 스레드가 된다.

        //메인스레드와 함께 스레드가 2개가 되었다.

        //0부터 5까지 합계를 구하는 스레드가 되었다.

        ThreadDemo demo1 = new ThreadDemo("first");

        

        //인스턴스가 별개로 생성됨으로 스레드가 3개가 되었다.

        //0부터 5까지 합계를 구하는 스레드가 되었다.

        //원래 스레드는 각각 다른 클래스에 해야한다.

        ThreadDemo demo2 = new ThreadDemo("second");

        

              

        

        demo1.start();

        demo2.start();

        

    }

}



※결과값

first:0

second:0

first:1

second:1

first:3

second:3

second:6

first:6

second:10

first:10






package prjThread;

class ThreadDemo extends Thread{

    //private String name;

    

    /*

    //어떤 스레드가 작업하는지 확인하기 위해 변수를 만든다.

    private String name;

    public ThreadDemo(String name) {

        this.name = name;

    }    

    */

    

    /*

    public ThreadDemo(String name) {

        

        super(name); //현재 스레드의 이름을 부모에게 저장시킨다.

    }

    */

    

    @Override

    //callback 우리가 호출하는게 아니라 시스템이 호출하는 메서드

    //run()은 콜백메서드이기 때문에 시스템에게 부탁만 할 수 있다.

    //오버라이드 규칙상 부모로부터 받은 메서드를 똑같이 써야하기 때문에 throws 예외처리를 할 수 없다.

    public void run() {

        

        //1~5까지 합계를 구한다.

        int sum = 0 ;

        for(int i=0; i<5; i++) {

            

            try {

                sleep(100);    //thread에서 0.1초 단위로 한 쪽을 쉬게 하는 메서드이다.

            } catch (InterruptedException e) {

                // TODO Auto-generated catch block

                e.printStackTrace();

            }

            

            

            sum += i;

            

            //1~5까지 합계를 출력한다.

            //임의로 이름을 붙힐 수 있도록 만들어 준다.

            //부모클래스에게 상속할 경우 getName()을 쓴다.

            System.out.println(getName() + ":" + sum);

            

            //출력을 해보면 순서는 실행 할 때 마다 다르지만 하나의 작업이 끝나고 두번째 작업을 한다.

        }

    }

    

}


public class ThreadTest1 {

    

    //메인스레드를 제외한 나머지는 자식스레드이다.

    public static void main(String[] args) {

    

        /*

        //부모가 스레드이고 부모로부터 상속 받았기 때문에 인스턴스도 스레드가 된다.

        //메인스레드와 함께 스레드가 2개가 되었다.

        //0부터 5까지 합계를 구하는 스레드가 되었다.

        ThreadDemo demo1 = new ThreadDemo("first");

        

        //인스턴스가 별개로 생성됨으로 스레드가 3개가 되었다.

        //0부터 5까지 합계를 구하는 스레드가 되었다.

        //원래 스레드는 각각 다른 클래스에 해야한다.

        ThreadDemo demo2 = new ThreadDemo("second");

        */

        

        //따로 이름을 붙이지 않아도 자식스레드에 이름을 붙이기 때문에 그 이름을 써도 된다.

        ThreadDemo demo1 = new ThreadDemo(); //시스템에게 호출해달라고 부탁한다.

        ThreadDemo demo2 = new ThreadDemo(); //시스템에게 호출해달라고 부탁한다.        

        

        demo1.start();

        demo2.start();

        

    }

}


※결과값

Thread-1:0

Thread-0:0

Thread-0:1

Thread-1:1

Thread-1:3

Thread-0:3

Thread-0:6

Thread-1:6

Thread-1:10

Thread-0:10






package prjThread;

//Runnable 인터페이스는 스레드가 해야할 기능을 물려받았지 스레드가 아니다.

class ThreadDemo implements Runnable{

    @Override

    public void run() {

        //1~5까지 합계를 구한다.

        int sum = 0 ;

        for(int i=0; i<5; i++) {

            

            try {

                //thread에서 0.1초 단위로 한 쪽을 쉬게 하는 메서드이다.

                //static 메서드이기 때문에 어디서나 불러올 수 있다.

                Thread.sleep(500);    

            } catch (InterruptedException e) {

                // TODO Auto-generated catch block

                e.printStackTrace();

            }

            

            

            sum += i;

            //run()메서드가 스레드를 연결하여 출력한다.

            System.out.println(Thread.currentThread().getName() +  ":" + sum);    

            

        }

    }

}


public class ThreadTest0 {

    //메인스레드를 제외한 나머지는 자식스레드이다.

    public static void main(String[] args) {

    ThreadDemo demo1 = new ThreadDemo();

    ThreadDemo demo2 = new ThreadDemo();

    

    Thread t1 = new Thread(demo1);

    Thread t2 = new Thread(demo2);

    

    t1.start();

    t2.start();

        

    }

}



※결과값

Thread-0:1

Thread-1:1

Thread-0:3

Thread-1:3

Thread-0:6

Thread-1:6

Thread-0:10

Thread-1:10




package prjThread;

class ThreadDemo2 extends Thread{//Thread를 상속받음

    @Override

    public void run() {             //run()은 시스템이 실행하는 메소드이다.

        System.out.println("자식 스레드 시작");

        

        //자식스레드릥 행동 1~9까지

        int cnt = 0;

        do {

            try {

                sleep(500);    //번갈아 가변서 일 할 수 있도록 0.5초 자식스레드에 쉬는 시간을 준다.

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            cnt++;

            System.out.print(cnt);

        }while(cnt<10);

        

        System.out.println("\n자식 스레드 종료\n");

    }    

    

}


public class ThreadTest2 {

    public static void main(String[] args) {

        

        System.out.println("메인 스레드 시작");

        

        ThreadDemo2 demo1 = new ThreadDemo2();    //ThreadDemo2 인스턴스가 스레드가 됨.

        demo1.start();                            //시스템에서 run()을 실행해달라고 요청.

        

        //자식스레드와 구별된 행동 1~9개 점 찍기

        int cnt = 0;

        do {

            try {

                Thread.sleep(200); //번갈아 가변서 일 할 수 있도록 메인스레드에 0.2초 쉬는 시간을 준다.

                                   //메인은 스레드클래스이지만 스래드 클래스는 아니기 때문에 Thread를 상속받는다.

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            cnt ++;

            System.out.print(".");

        }while(cnt<10);

        

        System.out.println("\n메인 스레드 종료\n");

    }

}



※ 결과값


메인 스레드 시작

자식 스레드 시작

..1..2...3..4.

메인 스레드 종료

5678910

자식 스레드 종료





Runnable 상속경우는 어떨까?



package prjThread;

class ThreadDemo2_1 implements Runnable{//Thread를 상속받지 못할 때는 Runnable인터페이스 상속

    String name;

    

    ThreadDemo2_1(String name){    //생성자를 생성

        this.name = name;        //인스턴스의 인자에 적힌 이름을 이름을 부모 클래스에 넣음

    }

    

    @Override

    public void run() {                                //run()은 시스템이 실행하는 메소드이다.

        System.out.println(name + "자식 스레드 시작");    //getName()을 이용해 부모생성자에 있는 이름을 가져옴.

        

        //자식스레드릥 행동 1~9까지

        int cnt = 0;

        do {

            try {

                Thread.sleep(500);    //번갈아 가변서 일 할 수 있도록 0.5초 자식스레드에 쉬는 시간을 준다.

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            cnt++;

            System.out.print(cnt);

        }while(cnt<10);

        

        System.out.println("\n자식 스레드 종료\n");

    }    

    

}


public class ThreadTest2_1 {

    public static void main(String[] args) {

        

        System.out.println("메인 스레드 시작");

        

        ThreadDemo2_1 demo1 = new ThreadDemo2_1("first");    //ThreadDemo2 demo1 인스턴스 생성

        ThreadDemo2_1 demo2 = new ThreadDemo2_1("second");    //ThreadDemo2 demo2 인스턴스 생성

        

        Thread t1  = new Thread(demo1); // Runnable에서 상속받은 클래스를 완전한 스레드기 되도록 만들어준다.

        Thread t2  = new Thread(demo2); // Runnable에서 상속받은 클래스를 완전한 스레드기 되도록 만들어준다.

        

        t1.start();                        //시스템에서 run()을 실행해달라고 요청.

        t2.start();                        //시스템에서 run()을 실행해달라고 요청.

        

        //자식스레드와 구별된 행동 1~9개 점 찍기

        int cnt = 0;

        do {

            try {

                Thread.sleep(200); //번갈아 가변서 일 할 수 있도록 메인스레드에 0.2초 쉬는 시간을 준다.

                                   //메인은 스레드클래스이지만 스래드 클래스는 아니기 때문에 Thread를 상속받는다.

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            cnt ++;

            System.out.print(".");

        }while(cnt<10);

        

        System.out.println("\n메인 스레드 종료\n");

    }

}


※ 결과값

메인 스레드 시작

first자식 스레드 시작

second자식 스레드 시작

..11..22...33..44.

메인 스레드 종료

556677889910

자식 스레드 종료

10

자식 스레드 종료


★ 이클립스 팁! ★

ctrl + alt + ↓(↑) = 이클립스에서 문장 복사 



package prjThread;

class ThreadDemo3 implements Runnable {

    

    String name;

    //Thread t; // 스레드 변수를 만들어 준다.

    

    ThreadDemo3(String n){

        name = n;

        //t = new Thread(this); //스레드의 인스턴스를 생성한다.

        //t.start(); //스레드 시작을 시스템에 요청한다.

        new Thread(this).start(); //자기 자신의 주소를 불러와 스레드 시작을 시스템에 요청한다.

    }

    

    @Override

    public void run() {

        for(int i=0; i<5; i++) {

            System.out.println(name + ":" + i);

        }

    }

}

public class ThreadTest3{

    public static void main(String[] args) {

        ThreadDemo3 d1 = new ThreadDemo3("스레드1"); //자기 자신의 주소가 인스턴스에 생성되었다.

        ThreadDemo3 d2 = new ThreadDemo3("스레드2"); //자기 자신의 주소가 인스턴스에 생성되었다.

        ThreadDemo3 d3 = new ThreadDemo3("스레드3"); //자기 자신의 주소가 인스턴스에 생성되었다.

    

        }

}


※ 결과값

스레드1:0

스레드1:1

스레드3:0

스레드3:1

스레드3:2

스레드3:3

스레드3:4

스레드2:0

스레드2:1

스레드2:2

스레드2:3

스레드2:4

스레드1:2

스레드1:3

스레드1:4




5. Thread의 Lifecycle


스레드의 일생을 알아보자.

스레드가 어떻게 생겨서 어떻게 사라지는가?


<실행>

★ thread()

 - 프로세스 작업의 최소 실행단위이다.


★ start()

 - 스레드는 반드시 Start 로 시작해야 한다.


★ run()

 - 스레드가 일을 하기 위해서는 

   Run()이라는 방 안에 들어가야만 한다.

   Run 방 안에는 1개의 

   스레드만 들어 갈 수 있다.

 - 모든 스레드는 run()으로 간다.



<비동기화 경우 휴식>

★ suspend()

- 스레드를 잠시 강제로 쉬도록 한다.

- 위험하다. 깨우지 않으면 계속 쉰다.

- 깨우기 위해 resume() 메서드를 사용한다.


★ sleep()

 - 일반적으로 가장 권장하는 휴게실이다.

 - 자동으로 start()에 돌아간다.


★ block

 - 시스템이 로딩 중일 때 다른 일을 할 수 

   있는 상태와 같다.

- 자동으로 start()로 돌아간다.


<동기화 경우 휴식>

★ wait()

  - 동기화 일 때만 쉬러 간다.

  - 동기화를 하기 위해 

     synchronization을 해야 한다.

  - 여러 개의 스래드가 동시에 같이 쉴 수 있다.



<종료>

1. 자연스럽게 종료 : 가장 좋다.

 

2. 강제종료 : 부득이한 경우에만 사용


   ① Stop

     - 무조건 바로 중지

     - 위험부담이 크다


   ② interrupt()

     - 여유를 주고 중지 


   ③ 직접처리

     - 가장 선호하는 방식




<동기화>

- 순서를 정해서 메모리(heap)에 접근하는 의미를 가지고 있다.

- 동시점근을 막고 접근 순서를 개발자가 통제하는 방법이다.

- 작업을 1번에 1개씩 처리해야 할 때 쓴다.

- 동기화가 깨지는 이유는 일을 동시에 처리하려고 하기 때문이다.

- 동기화가 깨졌을 때 문제가 생길 수도 있는 영역에 쓴다.

예 ) 돈을 ATM에 넣었는데 통장에는 없을 때



<동기화의 유의 할 점>

- 동기화를 하면 성능은 떨어진다.

- 동기화는 꼭 필요한 곳에서만 한다.


<대표적인 동기화 알고리즘>

1.세마포어 알고리즘

- 기다리다가 없는 공간이 있으면 들어간다.

2.뮤텍스 알고리즘

- 기다리다가 없는 공간이 있어도 줄서던 대로 기다린다.


동기화가 없다면 어떻게 될까?


package prjThread;

class Toilet{

    public void openDoor(String name) {

        System.out.println(name + " : 입장");

        

        

        for(int i=0; i<100000; i++) {

            if(i == 5000) {

                System.out.println(name + " : 끄응~~~");

            }

        }

        System.out.println(name + " : 퇴장");

        

        

    }

}


class Family extends Thread{

    Toilet toilet;

    String who;

    

    

    Family(Toilet t, String w){

        toilet = t;

        who = w;

    }

    

    @Override

    public void run() {

        toilet.openDoor(who); //who인자를 넣어 openDoor를 불러온다.

    }

    

    

}


public class SyncTest1 {

    public static void main(String[] args) {

        // TODO 왜 동기화인가?

        

        Toilet t = new Toilet();

        

        Family father = new Family(t, "아버지");

        Family mother = new Family(t, "어머니");

        Family brother = new Family(t, "형");

        Family sister = new Family(t, "누나");

        Family me = new Family(t, "나");

        

        father.start();

        mother.start();

        brother.start();

        sister.start();

        me.start();

        

    }

}


※ 결과값


어머니 : 입장

나 : 입장

아버지 : 입장

형 : 입장

나 : 끄응~~~

누나 : 입장

아버지 : 끄응~~~

어머니 : 끄응~~~

형 : 끄응~~~

누나 : 끄응~~~

형 : 퇴장

나 : 퇴장

누나 : 퇴장

어머니 : 퇴장

아버지 : 퇴장



1개의 화장실에는 꼭 1명만 가야한다.

그러나 동기화가 안된 상태에서 스레드를 동작하면

화장실에 여러명이 가는 대참사가 벌어진다.

1화장실에 1명씩 가도록 동기화하려면 어떻게 해야할까?


이럴때는 synchronized로 동기화처리하여 

스레드를 순차적으로 접근할 수 있도록 한다.

동기화는 반드시 메서드 단위로 붙여주어야한다.


package prjThread;

class Toilet{

    

    //synchronized로 동기화처리하여 스레드를 순차적으로 접근할 수 있도록 한다.

    public synchronized void openDoor(String name) {

        System.out.println(name + " : 입장");

        

        

        for(int i=0; i<100000; i++) {

            if(i == 5000) {

                System.out.println(name + " : 끄응~~~");

            }

        }

        System.out.println(name + " : 퇴장");

        

        

    }

}

class Family extends Thread{

    Toilet toilet;

    String who;

    

    

    Family(Toilet t, String w){

        toilet = t;

        who = w;

    }

    

    @Override

    public void run() {

        toilet.openDoor(who); //who인자를 넣어 openDoor를 불러온다.

    }

    

    

}


public class SyncTest1 {

    public static void main(String[] args) {

        // TODO 왜 동기화인가?

        

        Toilet t = new Toilet();

        

        Family father = new Family(t, "아버지");

        Family mother = new Family(t, "어머니");

        Family brother = new Family(t, "형");

        Family sister = new Family(t, "누나");

        Family me = new Family(t, "나");

        

        father.start();

        mother.start();

        brother.start();

        sister.start();

        me.start();

        

    }

}


※ 결과값

아버지 : 입장

아버지 : 끄응~~~

아버지 : 퇴장

나 : 입장

나 : 끄응~~~

나 : 퇴장

누나 : 입장

누나 : 끄응~~~

누나 : 퇴장

형 : 입장

형 : 끄응~~~

형 : 퇴장

어머니 : 입장

어머니 : 끄응~~~

어머니 : 퇴장



6. synchronized


<사용법>

1) 메서드 앞에 사용 할 때

2) 특정 변수나 메서드 내에 일부코드를 사용하고 싶을 때 

- 공유객체는 인스턴스 관련 코드들이다.

- 여기서 공유객체란 사용할 수 있는 범위를 정해주는 값이다.


synchronized(공유객체) {

        코드

}


<컬렉션과 동기화>

컬렉션에서 ArrayList 는 비동기화되어있지만, vector는 동기화가 되어 있다.

가급적이면 컬렉션에서 ArrayList를 써야하는 이유가 이 때문이다.



<Stack 컬렉션>

- 스택메모리와 똑같이 사용할 수 있도록 만든 컬렉션이다.

- 집어넣을 때는 push

- 꺼낼때는 pop이다.


자판기의 돈을 먹고 음료를 먹어보자.




package prjThread;

import java.util.Stack;

//////////////////////////////////////////////////자판기

class AutoMachine{                                //음료수를 저장할 수 있는 공간을 마련한다. 컬렉션으로 준비하자

    Stack store = new Stack();                     //스택메모리와 똑같이 사용할 수 있도록 만든 컬렉션 (먼저 넣으면 나중에 나옴)

    

    

    //음료수를 넣을 때 (관리자용)

    synchronized void putDrink(String drink) {    //동기화 시켜서 음료수를 넣고 꺼내게 한다.

        store.push(drink);                        //음료수를 넣는다.

        notify();                                //비어있을 때는 꺼내지 않지만 안비워져 있을 때는 꺼내야한다.        

    }

    

    //음료수를 꺼낼 때 (사용자용)

    synchronized String getDrink() {            //동기화 시켜서 음료수를 넣고 꺼내게 한다.

        while(store.isEmpty())

            try {

                wait();                            //창고가 비워져 있을 때는 기다려야한다.

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            

        

        return store.pop().toString();  //음료수를 뺀다.

    }

    

}


////////////////////////////////////////////////음료수 공급자

                                              //음료수를 공급하는 제공하는 클래스이다.

class Producer implements Runnable{

    private AutoMachine auto;

    

    Producer(AutoMachine a){                   //생성자이다.

        auto = a;

    }

    

    @Override

    public void run() {                          //자판기에 음료수를 집어넣는 작업을 할 수 있다.

        for(int i =0; i<10 ; i++) {           //음료수 10개를 집어넣겠다.

                                              // 공급자가 음료수를 넣는다.

        System.out.println(Thread.currentThread().getName() +

                             " : 음료수 No. " + i + "채워넣음");

        auto.putDrink("음료수 No." + i);

        

                                              //음료수 넣는 속도를 늦춘다. 천천히 하나씩.

        try {

            Thread.sleep(500);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        

      }

    

   }

}


///////////////////////////////////////////////소비자

class Consumer implements Runnable{             //소비자를 만든다.

    private AutoMachine auto;

    

    Consumer(AutoMachine a){                  //생성자이다.

        auto = a;

    }

    @Override

    public void run() {

        for(int i=0; i<10; i++) {             //음료수를 친구 몪까지 10개를 꺼내간다.

                                             //소비자가 음료수를 꺼내먹었다.

            System.out.println(Thread.currentThread().getName() +

                              ":" + auto.getDrink() + "꺼내먹음");

                                            

            try {

                Thread.sleep(500);               //음료수 넣는 속도를 늦춘다. 천천히 하나씩.

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

    }

    

}


//////////////////////////////////////////////main

public class SyncTest2 {

    public static void main(String[] args) { // 자판기에 돈을 넣고 음료를 먹어보자

        AutoMachine auto = new AutoMachine();

        

        Producer hong = new Producer(auto);

        Consumer kim = new Consumer(auto);

        

        

                                             //스레드 생성자에 이름을 두개 넘겨줄 수 도 있다.

        Thread h = new Thread(hong, "홍길동");

        Thread k = new Thread(kim, "김유신");

        

        h.start();

        k.start();

        

    }

}


※ 결과값

홍길동 : 음료수 No. 0채워넣음

김유신:음료수 No.0꺼내먹음

홍길동 : 음료수 No. 1채워넣음

김유신:음료수 No.1꺼내먹음

홍길동 : 음료수 No. 2채워넣음

김유신:음료수 No.2꺼내먹음

홍길동 : 음료수 No. 3채워넣음

김유신:음료수 No.3꺼내먹음

홍길동 : 음료수 No. 4채워넣음

김유신:음료수 No.4꺼내먹음

홍길동 : 음료수 No. 5채워넣음

김유신:음료수 No.5꺼내먹음

홍길동 : 음료수 No. 6채워넣음

김유신:음료수 No.6꺼내먹음

홍길동 : 음료수 No. 7채워넣음

김유신:음료수 No.7꺼내먹음

홍길동 : 음료수 No. 8채워넣음

김유신:음료수 No.8꺼내먹음

홍길동 : 음료수 No. 9채워넣음

김유신:음료수 No.9꺼내먹음



교착상태 (Dead Lock) : 오도가도 못하는 상태

교착상태 때는 강제종료를 해야한다.


7. 강제종료

1) stop()

2) interrupt()

3) 직접처리



무한 실행되는 클래스를 만들어보자



package prjThread;

class StopDemo implements Runnable{//Runnable인터페이스를 상속받는다.

    @Override

    public void run() { //Thread에 필수적인 run메소드를 오버라이드 한다.

        try {            //예외처리를 해준다.

            while(true) {

                System.out.println("Thread is alive...");

                Thread.sleep(500); //0.5초 간격으로 메시지가 나오게끔 한다.

            }

        }catch(InterruptedException err){

            

        }

        finally{

            System.out.println("Thread is dead...");

        }

    }

    

    

}

public class StopTest {

    public static void main(String[] args) {

        // TODO Auto-generated method stub

        StopDemo demo = new StopDemo();

        Thread t = new Thread(demo);

        t.start();

    }

}


※ 결과값

Thread is alive...

Thread is alive...

Thread is alive...

Thread is alive...

Thread is alive...

Thread is alive...

Thread is alive...

Thread is alive...

Thread is alive...


stop 메소드를 써서 멈춰보자.


package prjThread;

class StopDemo implements Runnable{//Runnable인터페이스를 상속받는다.

    @Override

    public void run() { //Thread에 필수적인 run메소드를 오버라이드 한다.

        try {            //예외처리를 해준다.

            while(true) {

                System.out.println("Thread is alive...");

                Thread.sleep(500); //0.5초 간격으로 메시지가 나오게끔 한다.

            }

        }catch(InterruptedException err){

            err.printStackTrace();

        }

        finally{

            System.out.println("Thread is dead...");

        }

    }

    

    

}

public class StopTest {

    public static void main(String[] args) throws InterruptedException {

        // TODO Auto-generated method stub

        StopDemo demo = new StopDemo();

        Thread t = new Thread(demo);

        t.start();

        

        Thread.sleep(3000);    //3초 후에

        t.stop();            //끝난다.

    }

}


※ 결과값

Thread is alive...

Thread is alive...

Thread is alive...

Thread is alive...

Thread is alive...

Thread is alive...

Thread is dead...



stop 메서드는 스래드를 끝낼 때 

여지를 주지 않기 때문에 문제가 있어 잘 쓰지 않는다.

또한 try~ catch 문에서 catch가 실행되지 않는다.

stop 메서드는 바로 꺼지기 때문에 예외처리를 할 수 없기 때문이다.



package prjThread;

class StopDemo2 implements Runnable{//Runnable인터페이스를 상속받는다.

    @Override

    public void run() { //Thread에 필수적인 run메소드를 오버라이드 한다.

        try {            //예외처리를 해준다.

            while(true) {

                System.out.println("Thread is alive...");

                Thread.sleep(500); //0.5초 간격으로 메시지가 나오게끔 한다.

            }

        }catch(InterruptedException err){

            err.printStackTrace();

        }

        finally{

            System.out.println("Thread is dead...");

        }

    }

    

    

}

public class StopTest2 {

    public static void main(String[] args) throws InterruptedException {

        // TODO Auto-generated method stub

        StopDemo2 demo = new StopDemo2();

        Thread t = new Thread(demo);

        t.start();

        

        Thread.sleep(3000);    //3초 후에

        t.interrupt();            //끝난다.

    }

}


※ 결과값

Thread is alive...

Thread is alive...

Thread is alive...

Thread is alive...

Thread is alive...

Thread is alive...

java.lang.InterruptedException: sleep interrupted

Thread is dead...

    at java.lang.Thread.sleep(Native Method)

    at prjThread.StopDemo2.run(StopTest2.java:10)

    at java.lang.Thread.run(Unknown Source)


결과값을 보면 catch 값으로 예외처리 된 오류메세지가 보인다.

이처럼 interrupt 메서드는 stop 메서드보다 안전하다.

개발자가 예외처리를 할 수 있기 때문이다.


사용자가 임의로 스레드를 멈추는 방법을 살펴보자.

직접 제어하는 방법이 가장 안전하다.


package prjThread;

class StopDemo3 implements Runnable{//Runnable인터페이스를 상속받는다.

    private boolean stopped = false;//처음 결과값을 false로 준다.

    

    void off() {

        stopped = true;

    }

    

    

    @Override

    public void run() {             //Thread에 필수적인 run메소드를 오버라이드 한다.

        try {                        //예외처리를 해준다.

            while(!stopped) {        // stopped가 true가 되면 끝나게 한다.

                System.out.println("Thread is alive...");

                Thread.sleep(500);  //0.5초 간격으로 메시지가 나오게끔 한다.

            }

        }catch(InterruptedException err){

            err.printStackTrace();

        }

        finally{

            System.out.println("Thread is dead...");

        }

    }

    

    

}

public class StopTest3 {

    public static void main(String[] args) throws InterruptedException {

        // TODO Auto-generated method stub

        StopDemo3 demo = new StopDemo3();

        Thread t = new Thread(demo);

        t.start();

        

        Thread.sleep(3000);    //3초 후에

        demo.off();          //이처럼 사용자가 임의로 스레드를 멈출 수 있다.

    }

}



※ 결과값

Thread is alive...

Thread is alive...

Thread is alive...

Thread is alive...

Thread is alive...

Thread is alive...

Thread is dead...





8. 종료시점을 정하는 방법

- 스레드가 끝나는 시점이 제 각각일 때 마무리가 애매해질 수 있다.

- 끝나는 시점을 동일하게 처리할 수 있는 방법이 필요하다.


1) isAlive()

- 스레드한테 살아있냐고 물어보고 다른 스레드가 살아있으면 끝날 때 까지 기다린다.

- 스레드가 끝나는 것을 사용자가 직접 처리해야하기 때문에 어렵다.


2) join()

- 다른 스레드가 안끝났다면 스레드가 동시에 끝날 때 까지 돕는다.

- 스레드가 끝나는 것을 자동으로 해준다. 


isAlive() 를 사용해보자.




package prjThread;

class FinishDemo extends Thread{//Thread를 상속받음

    FinishDemo(String name){    //생성자를 생성

        super(name);             //인스턴스의 인자에 적힌 이름을 이름을 부모 클래스에 넣음

    }

    

    @Override

    public void run() {                                    //run()은 시스템이 실행하는 메소드이다.

        System.out.println(getName() + "자식 스레드 시작"); //getName()을 이용해 부모생성자에 있는 이름을 가져옴.

        

        //자식스레드릥 행동 1~9까지

        int cnt = 0;

        do {

            try {

                sleep(500);    //번갈아 가변서 일 할 수 있도록 0.5초 자식스레드에 쉬는 시간을 준다.

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            cnt++;

            System.out.print(cnt);

        }while(cnt<10);

        

        System.out.println("\n자식 스레드 종료\n");

    }    

    

}

public class FinishTest {

    public static void main(String[] args) {

        

        System.out.println("메인 스레드 시작");

        

        FinishDemo demo1 = new FinishDemo("first");    //ThreadDemo2 demo1 인스턴스가 스레드가 됨.

        FinishDemo demo2 = new FinishDemo("second");    //ThreadDemo2 demo2 인스턴스가 스레드가 됨.

        

        

        demo1.start();                            //시스템에서 run()을 실행해달라고 요청.

        demo2.start();                            //시스템에서 run()을 실행해달라고 요청.

        

        //자식스레드와 구별된 행동 1~9개 점 찍기

        int cnt = 0;

        do {

            try {

                Thread.sleep(200); //번갈아 가변서 일 할 수 있도록 메인스레드에 0.2초 쉬는 시간을 준다.

                                   //메인은 스레드클래스이지만 스래드 클래스는 아니기 때문에 Thread를 상속받는다.

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            cnt ++;

            System.out.print(".");

        }while(cnt<10);

        

        System.out.println("\n메인 스레드 종료\n");

    }

}


※ 결과값

메인 스레드 시작

second자식 스레드 시작

first자식 스레드 시작

..11...22..33..44...55..66...77..88...99..1010

자식 스레드 종료

자식 스레드 종료

.

메인 스레드 종료


isAlive()는 죽었는지 살았는지 물어보기만 할 뿐 

처리를 하지 않아 개발자가 직접 조건을 넣어야한다.


.join() 메서드를 보자.




package prjThread;

class FinishDemo extends Thread{//Thread를 상속받음

    FinishDemo(String name){    //생성자를 생성

        super(name);             //인스턴스의 인자에 적힌 이름을 이름을 부모 클래스에 넣음

    }

    

    @Override

    public void run() {                                    //run()은 시스템이 실행하는 메소드이다.

        System.out.println(getName() + "자식 스레드 시작");//getName()을 이용해 부모생성자에 있는 이름을 가져옴.

        

        //자식스레드릥 행동 1~9까지

        int cnt = 0;

        do {

            try {

                sleep(500);    //번갈아 가변서 일 할 수 있도록 0.5초 자식스레드에 쉬는 시간을 준다.

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            cnt++;

            System.out.print(cnt);

        }while(cnt<10);

        

        System.out.println("\n자식 스레드 종료\n");

    }    

    

}


public class FinishTest {

    public static void main(String[] args) throws InterruptedException { //예외처리를 해준다.

        

        System.out.println("메인 스레드 시작");

        

        FinishDemo t1 = new FinishDemo("first");    //ThreadDemo2 demo1 인스턴스가 스레드가 됨.

        FinishDemo t2 = new FinishDemo("second");    //ThreadDemo2 demo2 인스턴스가 스레드가 됨.

        

        

        t1.start();                            //시스템에서 run()을 실행해달라고 요청.

        t2.start();                            //시스템에서 run()을 실행해달라고 요청.

        

        //자식스레드와 구별된 행동 1~9개 점 찍기

        int cnt = 0;

        do {

            try {

                Thread.sleep(200); //번갈아 가aus서 일 할 수 있도록 메인스레드에 0.2초 쉬는 시간을 준다.

                                   //메인은 스레드클래스이지만 스래드 클래스는 아니기 때문에 Thread를 상속받는다.

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            cnt ++;

            System.out.print(".");

            

        }while(cnt<10);

        //while(t1.isAlive() || t2.isAlive()); //.isAlive()로 메인스레드가 자식스레드가 끝날 때 까지 기다리게끔 한다.

        

        t1.join();

        t2.join();

        

        System.out.println("\n메인 스레드 종료\n");

    }

}


※ 결과값

메인 스레드 시작

second자식 스레드 시작

first자식 스레드 시작

..11..22...33..44.55667788991010

자식 스레드 종료

자식 스레드 종료

메인 스레드 종료



t1이 끝날 때 까지 메인메서드가 기다린다.

t2가 끝날 때 까지 메인메서드가 기다린다.

메인이 가장 마지막에 끝난다.

.join() 메서드가 알아서 이를 처리한다.

+ Recent posts