(Java멀티쓰레드 디자인패턴)1장 Java언어의 쓰레드

한문장 실행->두번째 문장 실행->세번째 문장 실행

처리의 흐름이 계속 한 줄의 실처럼 이어지는 프로그램을 싱글 쓰레드 프로그램이라고 한다.
싱글쓰레드 프로그램에서는 [프로그램을 실행하고 있는 주체]는 한개 뿐이다.


*메인클래스를 생성하면 메인쓰레드가 1개 실행된다. 그러나 엄밀히말하면 무대 뒤에서도 동작한다. 가비지콜렉션(Garbage Collection)용 쓰레드나 GUI관련 쓰레드 등이 존재한다.


<멀티쓰레드 프로그램>

*GUI응용프로그램
*시간이 걸리는 I/O처리 (자바에서는 java.nio패키지를 제공한다.)
*복수 클라이언트


<Thread클래스의 run메소드와  start메소드>

쓰레드를 기동시킬 때는 java.lang.Thread클래스를 사용한다.
1. 상속 받는 경우
예)
public class MyThread extends Thread{

  public void run()
{
   for(int i=0;i<10000;i++)
    {
      System.out.println("Nice");
    }
}
}

--->새로 기동되는 쓰레드의 동작은 run()메소드에서 기술한다.
run메소드가 종료되면 쓰레드도 종료된다.


2. 쓰레드 클래스의 인스턴스를 생성한 예제

public class Main{
   public static void main(String[] args){
     MyThread t=new MyThread();
     t.start();
     for(int i=0;i<10000;i++)
     {
       System.out.print("Good!");
     }
  }
}

start메소드를 호출하면 새로운 쓰레드가 기동됩니다.
start메소드를 호출하면 Java의 실행처리계는 무대 뒤편에서 새로운 쓰레드를 기동합니다.
그리고 그 새로운 쓰레드는 run메소드를 불러냅니다.

즉, 메인쓰레드의 인스터스 t.start()메소드를 호출하면 Thread를 상속받은 클래스안의 run메소드를 호출하며 그 안의 내용을 실행한다.
결국 Good!과 Nice!가 병행되며 실행된다. 이는 메인쓰레드와 생성된 쓰레드 2개가 동시에 동작하는 것이다.

****순차, 병렬, 병행의 개념

1. 순차(sequential)=> 복수의 업무를 [순서대로 처리]해 나가는 모습을 표현한 말이다. 예를들어, 한명이 10개의 업무를 처리할 때 그 사람이 업무를 순차적으로 처리해 나가는 것이다.
2. 병렬(parallel)=>복수의 업무를 [동시에 처리]하는 모습을 표현한 것이다. 예를들어, 10개의 업무를 2명이서 분담하여 처리할 때 두 사람이 업무를 병렭적으로 처리해 나가는 것과 같은 이치이다.
3. 병행(concurrent)=> 순차,병렬보다 추상도가 높은 표현이다. 이것은 한개의 업무를 [어떠한 순서로 처리하든 상관없는 여러개의 작업]으로 분할하는 모습을 표현한 말이다. 10개의 업무를 2종류의 독립된 작업으로 분할해 두면 병행처리 할 수 있는 준비가 끝난다.
작업자가 한명이라면 병행처리 할 수있도록 부나랳 둔 작업을 순차적으로 처리하게 되지만, 작업자가 2명이라면 같은 작업을 병렬적으로 처리 할 수도 있다.



<<<쓰레드의 기동>>>>
1. Thread클래스의 서브클래스 인스턴스를 사용하여 쓰레드를 기동한다.
2.  Runnable인터페이스의 구현 클래스를 인스턴스를 사용하여 쓰레드를 기동한다.


1.
public class Main{
  public static void main(String[] args)
   {
     new PrintThread("Good").start();  //이것은 PrintThread클래스의 인스턴스를 만들어내고 그 인스턴스의 start메소드를 호출하는 문이다.
     new PrintThread("Nice!").start();
   }
}

---->>>>중요한 것은 PrintThread의 인스턴스를 만든다와 그 인스턴스에 대응하는 쓰레드를 기동한다는 어디까지나 별개의 처리이다. 즉 PrintThread의 인스턴스가 만들어졌다고 해서 쓰레드가 기동하는 거은 아니며 쓰레드가 종료했다고 해서 PrintThread인스턴스가 사라져버리는 것은 아니다.
한편 , 메인쓰레드는 Main클래스의 main메소드 안에서 2개의 쓰레드를 기동시킨다. main메소드는 그 후 곧바로 종료되므로 메인쓰레드도 바로 종료되어 버린다. 그래도 프로그램 전체는 아직 종료되지 않았다. 기동한 2개의 쓰레드는 표시가 끝날 때까지 살아있기 떄문이다. 모든 쓰레드가 종료될 때까지 프로그램은 종료되지 않는다. 모든 쓰레드가 종료되야 프로그램도 종료된다.


**Java 프로그램은 데몬 쓰레드를 제외한 모든 쓰레드가 종료한 시점에서 종료된다. 데몬쓰레드란 무대 뒤에서 이뤄지는 작업을 시키기 위한 쓰레드이며, setDaemon메소드를 사용하여 데몬 쓰레드를 만들 수 있다.


<쓰레드를 기동한다 (2)-Runnable 인터페이스를 사용>

Runnable인터페이스는 java.lang패키지에 있는 인터페이스이다.
다음과 같이 선언된다.

public interface Runnable{
  public abstract void run();
}

예)
public class Printer implements Runnable{
  private String message;
 
  public String(String message)
 {
   this.message=message;
 }

 public void run()
  {
   for(int i =0;i<10000;i++)
   {
      System.out.println(message);
   }
  }
}

//메인클래스
public class Main{
   public static void main(String[] args)
  {
   new Thread(new Printer("Good")).start(); //Thread의 인스턴스를 만들 때 Printer클래스의 인스턴스를 생성자 인수로 건낸다.
   new Thread(new Printer("Nice")).start();
  }
 }

______>앞의 문장은 다음과 같이 3개의 문장으로 쓸  수 있다.
Runnable r=new Printer("Good");
Thread t=new Thread(r);
t.start();

Runnable 인터페이스를 구현한 클래스를 만들고 그 인스턴스를 Thread의 생성자에게 주어 start메소드를 호출한다.


*******java.util.concurrent 패키지에는 쓰레드 생성을 추상화시킨 ThreadFactory인터페이스가 포함되어 있다. 이 인터페이스를 사용하면 Runnable을 인수로 부여하여 Thread의 인스턴스를 new로 작성하는 부분을 ThreadFactory내부에 숨길 수 있다.

public class Main
{
 public static void main(String[] args)
 {
  ThreadFactory factory=Executors.defaultThreadFactory();
  Factory.newThread(new Printer("Nice")).start();
  for(int i=0;i<100;i++)
   System.out.println("Good");

 }
}


<<<<<<<<<쓰레드의 일시정지>>>>>>>>>>>>>>

Thread 클래스의 sleep메소드를 사용한다.
예)
public class Main
{
  public static void main(String[] args)
  {
    for(int i=0;i<10000;i++)
    {
      System.out.println("Good");
      try{
         Thread.sleep(1000);
      }catch(InterruptedExceptiom e){

    }

    }

}

}

--->>>중간에 깨우기 위해서는 Interrupt메소드를 사용한다.


<<<<<쓰레드의 배타제어>>>>>>>>>

쓰레드 A와 쓰레드 B가 병행하여 동작하는 경우 타이밍에 따라서 쓰레드 A의 "잔고확인"과 "잔고 줄이는 처리"라고 하는 2개 처리 사이에 쓰레드 B의 처리가 끼어들 가능성이있다.
즉, 쓰레드 A와 쓰레드 B가 경쟁(레이스)함으로써 예기치 않게 발생하는 이러한 상황을 데이터레이스 혹은 레이스컨디션(race condition)이라고 한다.

이러한 것을 정리하는 것을 교통정리라고하고 배타제어 혹은 상호배타(mutual exclusion)이라고 한다.
자바에서는 synchronized키워드를 사용한다.

************synchronized 메소드

synchronized 키워드를 붙이면 한번에 한개 쓰레드만 실행시킬 수 있다.
동기화라고 한다.
public class bank {

   private int money;
   private String name;
 
   public bank(int money,String name)
   {
  this.money=money;
  this.name=name;
   }
 
   //예금한다.
   public synchronized void deposit(int m)
   {
  money+=m;
   }
   //인출한다.
   public synchronized boolean withdraw(int m)
   {
  if(money>=m){
  money-=m;
  return true;
  }
  else{
  return false;
  }
 
   }
   public String getName()
   {
  return name;
   }

}

---->>>하나의 쓰레드가 Bank인스턴스의 deposit메소드를 실행하는 중에는 다른 쓰레드가 동일한 인스턴스의 deposit메소드나 withdraw메소드를 실행할 수 없습니다.
실행을 하려면 기다려야 한다. 단 getName()는 실행 가능하다
어떤 인스턴스에 관한 synchronized메소드는 한번에 하개 쓰레드밖에 실행할 수 없습니다.
synchronized가 아닌 메소드는 2개이상의 쓰레드를 실행 할 수 있습니다.


synchronized메소드 왼쪽에 락이라는 사각형을 두어 한개의 쓰레드만 접근 가능하게 한다.
락은 인스턴스 마다 존재한다. 따라서 어떤 인스턴스의 synchronized메소드를 실행하고 있다고 해서 다른 인스턴스 synchronized메소드를 실행할 수 없는 것이 아니다.

*****락과 모니터********
쓰레드의 배타제어를 하는 구조를 모니터(monitor)라 하며 락을 취하고 있는 것을 "모니터를 소유(own)한다" 혹은 "락을 홀드"한다고 한다.
현재의 쓰레드가 어떠한 객체의 락을 설정하고 있는지 여부는 Thread.holdLock메소드로 조사 할 수 있따. 현재의 쓰레드가 객체 obj락을 설정하고 있다는 내용은 assert를 사용하여 표현 할 수 있다.
assert Thread.holdsLock(obj);



<<<<synchronized 블록>>>>>
메소드의 전체가 아니라 메소드 일부를 하나의 쓰레드로 동작하게 하고싶은 경우에 사용한다.

예)
 void methid(){
  synchronized(this){
  
  }
   }

괄호 안에는 락을 취하는 인스턴스를 부여한다.

<<synchronized한 클래스 메소드와 synchronized블록>>
synchronized한 클래스 메소드는 한번에 한개 쓰레드 밖에 사용할 수 없습니다.
그 점은 synchronized한 인스턴스 메소드와 동일합니다. 그러나 synchronized한 클래스 메소드가 사용하는 락은 synchronized한 인스턴스 메소드가 사용하는 락과 다릅니다.

class Something{
static synchronized void method(){
}
}
이것은 다음과 같이 메소드 본체를 synchronized블록으로 구성한 메소드와 똑같이 기능한다.

class Something{
static void method(){
synchronized(something.class){
}
}
}

즉, synchronized한 클래스 메소드에서는 쓰레드의 배타제어를 실행하기 위해 그 클래스의 클래스 객체 락이 사용된다.

댓글

이 블로그의 인기 게시물

(ElasticSearch) 결과에서 순서 정렬

(네트워크)폴링방식 vs 롱 폴링방식

(18장) WebSocekt과 STOMP를 사용하여 메시징하기