여기선, 스레드 프로그램을 하는 기법, 동기화 기법에 대해서 설명합니다.

윈도우에서 멀티태스킹 기법! 가장 두드러지죠.. 여러가지 프로세스(프로그램)을 동시에 실행이 가능합니다.
스레드 기법을 이용하면, 하나의 프로세스에서도 여러가지의 메소드들을 동시에 실행이 가능하게 할수 있습니다. 무슨말이냐하면.. ! 프로그램 하나가 하나의 동작만하고 있으면, 개발의 한계가 있겟죠, 멋있는 프로그램도 안나오고, 때문에 스레드라는 기법을 이용해서 간지나게 프로그램을 만들수가 있다는 것입니다.

스레드는 하나의 프로그램 내에서 실행되는 메서드입니다, 같은 순간에 두 개의 메서드가 동시에 실행되면 두 개의 스레드가 동작하는 것입니다. 메소드를 여러개 실행하게 구현하도록 가능케 한 기법입니다.

스레드 프로그램을 만들때, 주의할 점은 우선권, 동기화 입니다. 여러개의 스레드가 실행되는 것은, 스레드간의 우선권이 있어, 자원을 공유해서 우선권대로 스레드들은 조금씩 순서대로 작업을 이루어집니다. 무슨말인지 말하기가 힘드네요..;; ..  
공유자원을 상대로 순서대로 작업이 이루어지는 것을 '동기화(Synchronization)가 보장된다'라고 말합니다.
(동기화를 꼭 해야하는 예를 들자면, 한 은행계좌를 서로 다른 쪽에서 동시에 돈을 뺄려고 하면, 어느쪽이 먼저 작업을..할건지가 정확해야한다는겁니다.)

                                                     스레드 클래스 생성자 를 보고 시작해보기로 하죠!
                                                            public Thread(Runnable target)


#1 Thread (C에서는 jump, 함수포인터로 구현한다.)
: 프로그램에 독립적으로 실행되는 메서드
: 스레드는 메서드다.

: 스레드 사용방법(2가지)
                   Runnable 인터페이스 사용              Thread클래스 자체를 상속받아 사용1
①Runnable은 하나의 메서드만 있는 인터페이스 입니다.
   (스레드는 하나의 메서드만 있으면 됩니다.)
②run을 구현합니다.
③이것을 스레드화 합니다.
출력화면을 보면, 번갈아가면서 두 스레드를 실행하는것을 알수있습니다.

public interface Runnable{
   void run();
}

class Top implements Runnable{
   public void run(){

      for(int i=0; i<10; i++)
         System.out.print(i+"\t");
   }
}


public class ThreadMain{
   public static void main(String[] args){
      System.out.println("프로그램 시작"); 
      Top t = new Top();     //스레드할 객체 생성
      Thread thd1 = new Thread((Runnable) t); //스레드에
      Thread thd2 = new Thread((Runnable) t); //객체장착.
      thd1.start();               //스레드 동작
      thd2.start();               //스레드 동작
      System.out.println("프로그램 종료");
   }
}

-----------------------------
프로그램시작
프로그램종료
0 0 1 1 2 2 3 3 4 4
5 5 6 6 7 7 8 8 9 9

①Thread클래스 자체를 상속받은 클래스에서 run을 구현한다
②객체를 만드는 동시에 스레드화한다.
출력화면을 보면 왼쪽과 같은 것을 볼수 있습니다.

class DerivedThread extends Thread{
    public void run(){
        for(int i=0; i<10; i++)
            System.out.print(i+"\t");
    }
}
 

public class Sample{
    public static void main(String[] args){
        System.out.println("프로그램 시작");
        DerivedThread d1 = new DerivedThread();
        DerivedThread d2 = new DerivedThread();
        d1.start();
        d2.start();
        System.out.println("프로그램 종료");
    }
}

-----------------------------
프로그램시작
프로그램종료
0 0 1 1 2 2 3 3 4 4
5 5 6 6 7 7 8 8 9 9

(참고로, 저는 위에 생성한 Runnable인터페이스에서 에러가걸리네요..그래서 그냥 자바에서 제공하는 걸로 바꿔서 했습니다.
(인터페이스는 지우고) 빨간색을 Runnable -> java.lang.Runnable 이렇게..)
자!
오른쪽이 더 편해보이지 않나요?ㅎㅎ비슷한가요ㅎㅎ 왼쪽의 경우는 인터페이스를 사용했는데, 자바 특징이 상속은 하나만 가능하기 때문입니다. 만약에 위에서 Top extends Frame 이런식으로 이미 상속을 받았다면, Thread는 상속이 불가합니다. 때문에 인터페이스를 사용해서 스레드를 구현할수 있습니다.  아래처럼..^_^
class Top extends Frame implements Runnable{
   public void run(){ 
      ... 
   }
}
이렇게 되면 RunFrame으로 가능한 업캐스팅은 
1. Frame으로의 캐스팅  :  Frame f = new Top();
2. Runnable로의 캐스팅 :  Runnable r = new Top();
입니다.



하나 더, 이미 상속을 받은상태면 위처럼 implements를 써도되지만, 코드가 복잡하다면 아래처럼 분리한 스타일도 있습니다.
                                                   Thread클래스 자체를 상속받아 사용2
import java.awt.*; 
//프레임(SoloFrame)과 스레드(SoloThread)를 분리시켜 구현했다.
                                                                            //스레드할 프레임을 생성하고 스레드 실행한다.        
class SoloFrame extends Frame{
   public SoloFrame(){
      
SoloThread t = new SoloThread(this);
      t.start();

   }

                                                           //setTitle을 사용하려면, SoloFrame의 참조값이 필요하므로 멤버변수를 둔다. 
class SoloThread extends Thread{
   private Frame f = null;
   public SoloThread(Frame f){
      this.f = f;                             //참조값 복사
   }
   public void run() {
      int i = 0;
      System.out.println("스레드 시작!");
      while(i<20) {
         System.out.print(i + "\t");
         f.setTitle("스레드 동작중" + i++);
         try{
            this.sleep(300);            //스레드를 일정기간 대기 
         }catch(InterruptedException e){System.out.println(e);}
      }
      System.out.println("스레드 종료!");
   }
}  
                                                                             //SoloFrame 객체생성과 동시에 스레드화합니다.                
public class SoloFrameMain{
   public static void main(String args[]){
      SoloFrame s = new SoloFrame();
      s.setSize(300, 100);
      s.show();
   } 
}


#1-2
스레드 상태
Start 상태            : 시작 상태  /  스레드를 만든후 start()를 호출했을 때,
Runnable 상태     : 동작할 수 있는 상태  /  start()호출 후 바로 Runable 상태,
Run 상태             : 동작 상태  /  Runnable 상태에서 CPU에게 제어권을 할당받는 순간 Run,
NotRunnable 상태 : 대기 상태  /  Run도 아닌, Dead도 아닌...중간상태 (sleep():일정시간, wait()와 notify():수동)
                               다시 Runnable로 갈수 있는 대기상태이다. 이상태로 들어가면 다른 스레드의 우선순위가 높아진다.
Dead 상태           : 종료 상태  /  모든 작업을 마쳤을 때,


#1-3
NotRunnable 상태스레드 사용 예
1. sleep : ex)Thread.sleep(1000) => 1/1000초
2. 스레드 작업을 대기시키고, 다시 작업을 재개하는 상태로 바꿀수가 있는데, 이 메소드들은 쓰지 않습니다. 나중에 뒤에서 wat과 notify메소드로 다시 설명하겠습니다.
   public final void suspend() Deprecated
   public final void resume() Deprecated

스레드 종료
1. stop 메서드가 있긴한데 구식방식, 안전하지 않아(Deprecated) 사용하지 않는다고 합니다.
   public final void stop() Deprecated 
   public final void stop(Throwable obj) Deprecated
2. while문으로 run을 제어해서 사용한다. (권장방식.. 다들 이렇게 쓴다고 합니다.)
   예를 들면,
class TerminateThread extends Thread {
   private boolean flag = false; 
   public void run() {
      while(!flag) {                                                              //flag가 true가 되면 바로 스레드를 종료시킨다.
         try{   this.sleep(100);  }
         catch(InterruptedException e) {    }                           //sleep을 사용하려면 InterruptedException예외가 필요하다.
      }
   } 
   public void setFlag(boolean flag){  
      this.flag = flag; 
   }
}
public class TerminateThreadMain {
   public static void main(String args[])throws Exception{
      TerminateThread a = new TerminateThread();
      TerminateThread b = new TerminateThread();
      a.start();                                                                    //a스레드 실행!
      b.start();                                                                    //b스레드 실행!
      int i; 
  
      System.out.print("종료할 스레드를 입력하시오! A, B?\n");
      while(true){
         i = System.in.read();
         if(i == 'A'){                                                             //a스레드만 종료
            a.setFlag(true);
         }else if(i == 'B'){                                                    //b스레드만 종료
            a.setFlag(true); 
            b.setFlag(true); 
            System.out.println("main종료");
         }
         break;
      }   
   }
}












#2 스레드의 우선권(Priority)
스레드의 우선권에 따라 스레드 작업 순서가 다릅니다. Runnable상태에서 얼마나 Run으로 빨리 가느냐가 문제입니다.

Thread 클래스의 스태틱 우선권 상수입니다. 이 상수값으로 우선권을 정할 수 있습니다.
public static final int MIN_PRIORITY = 1;
public static final int NORM_PRIORITY = 5;
public static final int MAX_PRIORITY = 10;

: 우선권을 사용한 예를 들면,
class PriorityThread extends  Thread{
   public void run() {
      int i = 0; 
      System.out.print(this.getName());                                             //스레드의 이름 출력
      System.out.println("[우선권:" + this.getPriority() + "] 시작\t");      //설정된 우선권 얻기
      while(i < 10000) {
         i = i + 1; 
         try{
            this.sleep(1);
         }catch(Exception e){System.out.println(e);} 
      }
      System.out.print(this.getName());           
      System.out.println("[우선권:" + this.getPriority() + "] 종료\t");
   }


public class PriorityThreadMain {
   public static void main(String[] args) {
      System.out.println("Main start");
      for(int i=1; i<10; i++){
      //for(int i=Thread.MIN_PRIORITY; i<=Thread.MAX_PRIORITY; i++){
      PriorityThread s = new PriorityThread();
      s.setPriority(i);                                                                       //우선권 설정하기
      s.start();
      }
      System.out.println("Main end");
   }
}

---------------------------------출력화면
Main 시작
Thread-6[우선권:6] 시작
Thread-7[우선권:7] 시작
Thread-8[우선권:8] 시작
Thread-9[우선권:9] 시작
Thread-5[우선권:5] 시작
Thread-10[우선권:10] 시작
Main 종료
Thread-3[우선권:3] 시작
Thread-4[우선권:4] 시작
Thread-1[우선권:1] 시작
Thread-2[우선권:2] 시작
Thread-10[우선권:10] 종료
Thread-8[우선권:8] 종료
Thread-9[우선권:9] 종료
Thread-6[우선권:6] 종료
Thread-7[우선권:7] 종료
Thread-5[우선권:5] 종료
Thread-3[우선권:3] 종료
Thread-4[우선권:4] 종료
Thread-1[우선권:1] 종료
Thread-2[우선권:2] 종료

MIN_PRIORITY부터 MAX_PRIORITY까지 우선구너을 모두 사용해서 스레드를 생성하였습니다.결과는 보면 알듯이, 우선권이 높은 스레드가 먼저, 낮은 스레드가 나중에 종료되는 것을 볼 수 있습니다. 실제로 실행해보니.. 결과는 좀 다르지만, 우선권을 주어 스레드의 작업순서를 정하는 것입니다.













#3 Synchronization(동기화)

       멀티 스레드의 문제점(공유자원의 문제) 해결 = 동기화(Synchronization)기법 = 줄서기 = 순서대로 공유자원을 사용

 ATM기기에서 같은 계좌로 서로 다른 곳에서 입금, 출금을 동시에 합니다. 이때..  만약에 한쪽에서 입금 버튼을 클릭하고, ATM기기는 입금 처리 중..이라고 할때, 다른 한쪽에서는 동시에 출금 버튼을 클릭하고, ATM기기에서는 출금 처리..를 합니다. 이때, 한쪽 ATM기기에서는 입금처리 중일때, 그순간 반대쪽에서 출금을 해버린다면??!!

  자.. 계좌에 10000원이 있습니다.
  왼쪽 ATM에서 5000원을 입금합니다.(입금버튼 클릭)   
  ATM은 입금 처리를 하는 중입니다.   -> 그순간!!  -> 오른쪽 ATM에서 2000원을 출금합니다.(출금버튼 클릭)
                                                                           오른쪽 ATM이 좀더 빨라 출금을 먼저했습니다. 
                                                                           왼쪽 ATM보다 빨리 출금해서 계좌에는 10000-2000=8000원 남습니다.
  ATM이 느려서.. 이제 처리를 마쳤습니다.   <-  <- 
  입금버튼 눌렀을 때 돈으로 계산해서 10000+5000=15000원이 계좌에 있습니다.


 완벽한 ATM 시스템으로는 이 계좌에 10000-2000+5000=13000원이 있어야 합니다. 그런데, ATM에서 입력했던 그순간..값으로 계산을 해버리는 바람에 마지막에 처리된 돈인 15000원이 계좌에 남아있습니다. 이건 뭐.. ㅡㅡ;; 엉망징찬이네요..
그래서!!! 한쪽에서 ATM에서 계좌를 사용하면 그 계좌 처리과정이 끝나는 동안 그 계좌에 LOCK를 걸어놓는 것입니다. 그럼 그때는 아무도 못건드겠죠ㅎㅎ
 다시말하면, 공유자원(계좌)의 사용을 한쪽에서만 사용 하도록 LOCK을 걸어버리는 겁니다. 스레드로 다시 생각해보면, static 객체를 스레드로 사용하게 된다면?? 이때 이 static 객체는 공유자원으로 같은 메모리를 쓰게 됩니다. 이 메모리를 서로 다른 스래드에서 동시에 사용하게되면, 엉망징창이 되니깐, 이것에 LOCK을 걸어 방지하는 겁니다.

                                     LOCK을 거는 방법은 synchronized 키워드를 사용하는 것입니다.




그럼 사용해보죠..
                                                                       syncronized 사용
class Bank{
   private int money = 10000; 
   public int getMoney(){      return this.money;       }
   public void setMoney(int money){       this.money = money;      }
   public synchronized void saveMoney(int save){
      int m = this.getMoney();
      try{
         Thread.sleep(3000);
      }catch(InterruptedException e){e.printStackTrace();}
        this.setMoney(m + save);
      }
   public void minusMoney(int minus){
      synchronized(this){
         int m = this.money;
         try{
            Thread.sleep(200);
         }catch(InterruptedException e){e.printStackTrace();} 
            this.setMoney(m - minus);
         } 
      }    
   }
}

class Park extends Thread{
   public void run(){
      SyncMain.myBank.saveMoney(5000);
      System.out.println("saveMoney(3000):" + SyncMain.myBank.getMoney());
   }
}

class ParkWife extends Thread{
   public void run(){
      SyncMain.myBank.minusMoney(2000);
      System.out.println("minusMoney(3000):" + SyncMain.myBank.getMoney());
}
}

public class SyncMain{
   public static Bank myBank = new Bank(); 
   public static void main(String[] args) throws Exception{
      Park p = new Park();
      ParkWife w = new ParkWife();
      p.start();                                            //Park객체가 실행되는 동안 saveMoney에 LOCK을 걸고 합니다.
      try{
         Thread.sleep(200);
      }catch(InterruptedException e){e.printStackTrace();}
      w.start();                                     //위에서 먼저, 1/200초 동안 Mybank는 LOCK이 걸렸으므로 풀리면 시작합니다.
   }
}

synchronized의 사용방법
▧1 : synchronized 메서드로 사용할 경우, 메서드 내의 모든 멤버 변수는 LOCK(동기화 보장)이 걸립니다.
        saveMoney 메서드를 사용하는 동안은 메서드 내의 멤버 변수는 LOCK이 걸립니다.
class Bank{
   public synchronized void saveMoney(int save){  ..  }
}

▧2 : 메서드내에 블록을 사용하면 블록에서 명시한 객체 멤버변수들은 메서드가 끝날때까지 LOCK(동기화 보장)이 걸립니다.
        minusMoney 메서드를 사용하는 동안은 메서드 내의 this=(Bank)의 모든 멤버 변수는 LOCK이 걸립니다.
class Bank{
   public void minusMoney(int minus){
      synchronized(this){  ..  }
   }
}

▧3 : 2번과 같이 블록을 했고,  해당 객체만 synchronized만 명시해 주면 객체의 메소드는 모두 synchronized되어 멤버변수는 LOCK(동기화 보장)이 걸립니다.
  위 코드에서 모든 synchronized를 지우고 아래 처럼 수정하면, Bank객체는 하나의 메서드 작업을 하는동안 아무것도 건드리지 못하게 됩니다. (동기화가 보장되기는 하지만, 같은 객체가 동시에 다른 메서드를 사용할수 없으므로, 1번방법처럼메서드에만 synchronized를 사용해야 할 때도 있다. ex, 만약에 비디오가게에서 빌리기(), 보는중(), 반납하기() 메서드가 있을때, 보는중()메서드에서 lock을 걸어버리면, 비디오가게자체가 lock이 걸려버리니깐 그동안은 빌릴수도 반납할수도없으므로 비효율적이다.)
  SyncMain의 myBank의 모든 메서드는 사용하는 동안 메서드 내에서 myBank 변수는 LOCK이 걸립니다.
class ParkWife extends Thread{
   public void run(){ 
      syncrhonized(SyncMain.myBank){ 
         SyncMain.myBank.saveMoney(3000); 
         ...
      }
   }
}