2016년 3월 11일 금요일

안드로이드강좌 > 멀티쓰레드(Multi Thread) 를 손쉽게 구현해보자



안드로이드 강좌 > 멀티쓰레드 소스

멀티쓰레드(Multi Thread)는 프로세스(Process)안에서 여러개의 쓰레드가 동시에 동작하는것이다.
하나의 프로세스에 쓰레드가 여러개로 보면 된다.
마치 한컴퓨터에 여러개의 프로그램이 돌아가는거라보면 보면 여기서 한컴퓨터는 프로세스입장이고, 여러개의 프로그램은 쓰레드의 입장이라 보면 되겠다

java 1.5 버전 부터 java.util.concurrent 패키지가 생김으로 인해 멀티 쓰레드 프로그래밍을 손쉽게 구현할 수 있어요
예전에는 구현하기가 복잡했지만, 현재는 아주 간단하게 구현이 가능하다. 여러분은 쓰레드에 대한 몇가지 요소만 배우시면 돼요 
자바를 실행하면 main 쓰레드라 불리우는 것이 해당 객체의 main(String[]) 메소드를 실행해줘요
즉, 프로그램이 실행되면, 최소한 한 개 이상의 쓰레드는 동작한다 보시면 돼요

제목 : Multi Thread를 손쉽게 구현해보자




첫째. Thread 생성 및 실행
Thread 생생은 java.lang.Runnable 인터페이스를 이용하시거나, java.lang.Thread를 상속받아서 구현하는 방법이 있어요
여기서 생성한 Thread를 start() 메소드를 이용해서 작동시켜준다. 
이 메소드가 호출되면 자동으로 run()메소드가 실행이 된다. 
그래서 어떤 작업을 하고 싶으면, run() 메소드 안에 해당 작업을 구현해주면 된다

예제)
package test.thread;  

public class SimpleThread extends Thread {  
     public void run() {  
         for (int i = 0; i < 10; i++) {  
             System.out.printf("[%s] %d번째 : %n", Thread.currentThread().getName(), i);  
         }
     }
     
     public static void main(String[] args) throws InterruptedException {  
         Thread threadOne = new SimpleThread();  
         Thread threadTwo = new SimpleThread();  
         threadOne.start();
         threadTwo.start();
     }
 }

 =============================

둘째. 동기화
 - 기본적인 개념은 "volatile 에 대한 단상"(http://blog.kangwoo.kr/43)을 참고 바란다.
 - java.util.concurrent.Semaphore 클래스를 이용하면 자원 사용을 제어
   즉, 사용할 수 있는 자원이 최대 N개인데, N개보다 많은 수의 쓰레드가 그 자원을 필요로 할 경우 제어
 - java.util.concurrent.locks.ReentrantLock : 읽기/쓰기 락을 제어

셋째. Executor를 이용하여 Thread를 관리해준다
  - 보통 자원의 효율적 이용을 위해서 Thread Pool과 Queue를 만들어서 사용한다
  자바 1.5에서는 기본적으로 지원해준다. 
  java.util.concurrent.Executors을 이용하면 아주 손쉽게 사용할 수 있어요
   + java.util.concurrent.TimeUnit : 시간 단위를 나타낸다. 
     DAYS, HOURS, MICROSECONDS, MILLISECONDS, MINUTES, NANOSECONDS, SECONDS 가 있다.

   + java.util.concurrent.Executor : 처리할 작업을 실행할 수 있는 기능을 정의해준다
     + execute(Runnable) : 해당 작업을 실행한다. 
   쓰레드 풀이 구현된경우에는 전달받은 작업을 큐(Queue)에 넣은 후, 가용 쓰레드가 존재할 경우 작업을 실행한다

   + java.util.concurrent.ExecutorService : 
     Executor의 하위 인터페이스로서 생명주기(shutdown(), shutdownNow()를 관리할 수 있는 기능과 
     Callable을 사용할수 있는 기능을 정의하고 있다. 
     + shutdown() : 중지(shutdown) 명령을 내린다. 
   대기중인 작업은 실행되지만, 새로운 작업은 추가되지 않는다.
     + List<Runnable> shutdownNo() : 현재 실행중인 모든 작업 및 대기중인 작업 모두를 중지시킨다. 
   대기중인 작업 목록을 반환한다.
     + boolean isShutdown() : Executor가 중지(shutdown)가 되었는지 여부를 판단한다.
     + boolean isTerminated() : 모든 작업이 종료되었는지 여부를 판단한다.
     + boolean awaitTermination(long, TimeUnit) : 
   중지(shutdown) 명령을 내린후, 지정한 시간 동안 모든 작업이 종료될때까지 대기한다. 
   지정한 시간이내에 작업이 종료되면 true, 아니면 false를 반환한다.
     + Future submit(Callable task) : 결과값을 반환할수 있는 Callable 작업을 실행한다. 
   (Callable에 대해서는 뒤에서 다루겠다.)

   + java.util.concurrent.ScheduledExecutorService : 
     ExecutorService의 하위 인터페이스로서 스케줄에 따라 작업을 실행할 수 있는 기능을 정의하고 있다.
     + ScheduledFuture<?> schedule(Runnable, long, TimeUnit)  : 지정한 시간 이후에 작업을 실행한다.
   
   + java.util.concurrent.Executors : 
      자바 1.5에서 기본적으로 제공하는 Executor 구현체를 생성할 수 있는 메소드를 제공하는 유틸리티 클래스이다.
     + ExecutorService newFixedThreadPool(int) : 지정한 갯수만큼 쓰레드를 가질 수 있는 쓰레들 풀을 생성한다. (ThreadPoolExecutor)
     + ExecutorService newCachedThreadPool() : 재사용이 가능한 쓰레드 풀을 생성한다. (ThreadPoolExecutor)
     + ExecutorService newSingleThreadExecutor() : 단일 쓰레드만을 사용하는 ExecutorService 를 생성한다.
  + ScheduledExecutorService newScheduledThreadPool(int) : 
     스케줄 가능한 쓰레드 풀을 생성한다. (ScheduledThreadPoolExecutor)
     + ScheduledExecutorService newSingleThreadScheduledExecutor() : 
       단일 쓰레드만을 사용하는 스케줄 가능한 ExecutorService 를 생성한다.

   + java.util.concurrent.ThreadPoolExecutor
   
   + java.util.concurrent.ScheduledThreadPoolExecutor

> Executors 클래스를 가지고 간단한 멀티 쓰레드 프로그램 예제

package test.thread;  

import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  

public class MultiThreadUnit implements Runnable {  
     private int total;  
     public void run() {  
         for (int i = 1; i <= 30000; i++) {  
             total += i;  
         }  
         System.out.printf("[%s] 1에서 30000까지의 총 합은 %d : %n", Thread.currentThread().getName(), total);  
     }

 public static void main(String[] args) throws InterruptedException {  
         ExecutorService extService = Executors.newFixedThreadPool(3);  
         extService.execute(new MultiThreadUnit());
         extService.execute(new MultiThreadUnit());
         extService.execute(new MultiThreadUnit());
         extService.execute(new MultiThreadUnit());
         extService.execute(new MultiThreadUnit());
         extService.execute(new MultiThreadUnit());
         extService.execute(new MultiThreadUnit());
         extService.execute(new MultiThreadUnit());
         extService.shutdown();
     }
 }

> 출력 결과
[pool-1-thread-1] 1에서 20000까지의 총 합은 300010000
[pool-1-thread-2] 1에서 30000까지의 총 합은 300010000
[pool-1-thread-3] 1에서 30000까지의 총 합은 300010000
[pool-1-thread-1] 1에서 30000까지의 총 합은 300010000
[pool-1-thread-2] 1에서 30000까지의 총 합은 300010000
[pool-1-thread-3] 1에서 30000까지의 총 합은 300010000
[pool-1-thread-1] 1에서 30000까지의 총 합은 300010000
[pool-1-thread-2] 1에서 30000까지의 총 합은 300010000

> 위의 출력결과처럼 잘 돌아간다 : 아주 심플해서 편해요

* 참고로, 
  해당 데몬(daemon)이 종료되면, 
  더 이상을 요청을 받아서는 안된다. 그리고 기존에 받은 요청은 지정한 시간내에 끝내야한다
  이 경우에 아래처럼 구현할 수 있어요

소스)
void shutdownAndAwaitTerminate(ExecutorService pool) {  
   pool.shutdown();
   try {  
     if (!pool.awaitTermination(50, TimeUnit.SECONDS)) {  
       pool.shutdownNow();
       if (!pool.awaitTermination(50, TimeUnit.SECONDS))  
           System.err.println("Pool이 끝나지 않았다");  
     } 
   } catch (InterruptedException ie) {
     pool.shutdownNow();
     Thread.currentThread().interrupt();
   }
 }

여기서 일단락 마무리하고 다음

=======================================================

> Callabe로 쓰레드 구현하기
  참고사항 : Callable 인터페이스의 call() 메소드는 결과값을 반환하도록 구성돼있어
  하지만 call() 메소드를 이용해서 결과값을 반환받을때까지 기다리면, 그건 효용가치가 떨어진다
  그래서 이럴경우 Future 인터페이스를 사용한다

package test.thread;  

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  
import java.util.concurrent.Future;  

public class MultiThreadUnit implements Callable<INTEGER> {  
     private int total;  
     public Integer call() throws Exception {  
         for (int i = 1; i <= 30000; i++) {  
             total += i;  
         }  
         return total;  
     }

 public static void main(String[] args) throws InterruptedException, ExecutionException {  
         ExecutorService exctService = Executors.newFixedThreadPool(3);
         Future<INTEGER> ft1 = exctService.submit(new MultiThreadUnit());
         Future<INTEGER> ft2 = exctService.submit(new MultiThreadUnit());
         Future<INTEGER> ft3 = exctService.submit(new MultiThreadUnit());

         System.out.printf("[%s] 1에서 30000까지의 총 합은 %d : %n", "ft1", ft1.get());  
         System.out.printf("[%s] 1에서 30000까지의 총 합은 %d : %n", "ft2", ft2.get());  
         System.out.printf("[%s] 1에서 30000까지의 총 합은 %d : %n", "ft3", ft3.get());  
         exctService.shutdown();  
     }

예전에 비하여 구현하기가 많이 편리해졌어요
시간이 지나면 지날수록 개발이 편리해져요
개발하는것도 날아갈수록 발전되어야 하는데, 한사람이 모두 신기술을 마스터하기는 힘들다...
후손들에게 하나씩 전수해줘야지 ^^

즐거운 시간 보내세요~


댓글 없음:

댓글 쓰기