티스토리 뷰

Android/Android Multi Threading

4. 스레드 통신

안드로이드용용 2016. 7. 26. 16:22

지난시간까지 안드로이드의 스레드 종류와 우선순위에 대해서 알아보았다. 이번 시간에는 일반적인 스레드 통신 기법과 안드로이드에서의 스레드 통신 기법에 대해서 알아보도록 하자.


  • 단방향 데이터 파이프를 통한 데이터 전달
  • 공유 메모리 통신
  • 블로킹 큐를 사용하는 소비자-생성자 패턴 구현
  • 메시지 큐의 동작
  • UI 스레드로 태스크 전송


4.1 파이프

java.io 패키지의 일부

생산자 스레드는 파이프에 데이터를 기록하고 소비자 스레드는 파이프에서 데이터를 읽는다.

단방향 스레드이기 때문에 한쪽에서 읽기만하고 한쪽에선 쓰기만 한다.

소비자 생성자 패턴에 사용된다.


4.1.1 기본 파이프 사용 방법


  1. 연결 설정
    PipedReader r = new PipedReader();
    PipedWriter w = new PipedWriter();
    w.connect(r);
    

  2. 처리하는 스레드로 읽기를 전달한다.

    Thread t = new MyReaderThread(r);
    t.strart();
    

  3. 데이터를 전송한다.
    w.write('A')  // 생산자 스레드 : 하나의 문자 또는 문자 배열 쓰기
    int result = r.read(); //소비자 스레드 : 데이터 읽기
    
    //개선된 방법 : 생산자 스레드는 쓰기 후 파이프를 청소(flush)한다.
    w.write('A');
    w.flush(); 
    
    //소비자 스레드 : 반복문에서 데이터를 읽는다.
    int i;
    while((i = reader.read()) != -1) {
        char c = (char) i;
    }
  4.  연결을 닫는다.
  • w.close(); // 생산자 스레드를 닫는다. r.close(); //소비자 스레드를 닫는다.




  • 4.2 공유 메모리

    공유메모리인 힙을 사용하는 것은 스레드간 정보를 전달하는 일반적인 방법이다.

    모든 응용프로그램의 모든 스레드는 프로세스 안에서 같은 주소 공간에 접근할 수 있다.


    공유메모리 저장 객체

    • 인스턴스 멤버 변수
    • 클레스 맴버 변수
    • 메서드 안에 선언된 객체
    4.2.1 시그널링

    스레드가 공유 메모리에 상태 변수를 통해 통신하는 동안, 스레드는 상태의 변화를 가져오기 위해 상태값을 polling할 수 있다. 같은 공유 메모리에 서로다른 스레드가 접근할 수 있으므로 시그널링 메커지즘 동기화가 필요하다.

    동기화 방법은 3가지 방법이 있다.
    1. synchronized : 암시적 잠금으로 코드 블록에 sychronized 키워드 안에서 wait와 notify를 통해서 다른 접근하는 스레드를 차단하거나 풀어준다.
    2. ReentrantLock, ReentrantReadWriteLock : 명시적으로 await와 signal을 통해 스레드 시그널링을 조절할 수 있으며 자세한 사항은 다음강에 설명하겠다.

    4.3 블로킹 큐
    위에 설명한 스레드 시그널링은 정교한 설정은 가능하지만 개발자 실수에 의한 오류가 발생하기 가장 쉬운 방법이다. 이를 해결하기 위해서, 자바 플렛폼은 스레드 간 객체의 단방향 전달을 해결하기 위해 추상화 계층을 구축했다. (생산자 - 소비자 동기화 문제)
    java.util.concurrent.BlockingQueue가 그것이다.

    블로킹 큐는 스레드의 차단 뿐만 아니라, 리스트가 가득 차지 않았던가 비어 있지 않다는가와 같은 중요한 상태 변화의 시그널링도 담당한다.

    LinkedBlockingQueue 사용 예
    public class ConsumerProducer {
        private final int LIMIT = 10;
        private BlockingQueue blockingQueue = new LinkedBlockingQueue (LIMIT);
        
        public void produce() throws InterruptedException {
            int value = 0;
            while (true) {
                blockingQueue.put(value++);
            }
        }
    
        public void consume() throws InterruptedException {
            while (true) {
                int value = blockingQueue.take();
            }
        }
    }
    


    4.4 안드로이드 메시지 전달

    블로킹 큐를 안드로이드에서 사용하면 UI스레드가 차단될 수 있는 문제를 발생한다.

    때문에, 안드로이드에서 가장 일반적인 스레드 통신은 UI 스레드와 작업자 스레드 사이에 있다.


    UI스레드는 긴 태스크와 같은 처리를 백그라운드 스레드로 전송할 수 있다. 또한 백그라운드 스레드에서 처리가 완료된 데이터를 다시 UI스레드로 전송하여야 한다. 안드로이드는 이런 스레드 메시지 전달 메커니즘을 사용한다. 이 메시지 전달 메커니즘은 생산자 스레드와 소비자 스레드 모두 메시지가 전달되는 동안에 차단되지 않는 비차단적 소비자-생산자 패턴이다.


    안드로이드 스레드 통신 메커니즘을 알기 위해서는 android.os 패키지 안의 다음 사항을 알아야 한다.


    핸들러 - 루퍼 - 메시지 큐 - 메시지

    • android.os.Looper : UI 스레드에 하나가 존재하며 소비자 스레드와 연관된 메시지 발송자
    • android.os.Handler : 큐에 메시지를 삽입하는 생산자 스레드를 위한 인터페이스와 소비자 스레드 메시지 처리, 하나의 Looper 객체는 많은 핸들러를 갖지만 모두 같은 큐에 삽입
    • android.os.MessageQueue : 소비자 스레드에서 처리할 메시지들이 담긴 무제한의 연결 리스트. 모든 루퍼와 스레드는 최대 하나의 메시지 큐를 가진다.
    • android.os.Message : 소비자 스레드에서 실행하는 메시지



    예제)

    public class LooperActivity extends AppCompatActivity implements View.OnClickListener {
    
        LooperThread mLooperThread;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_looper);
            mLooperThread = new LooperThread();
            mLooperThread.start();
        }
    
        @Override
        public void onClick(View view) {
            if (mLooperThread.mHandler != null) {
                Message msg = mLooperThread.mHandler.obtainMessage(0);
                mLooperThread.mHandler.sendMessage(msg);
            }
        }
    
        private static class LooperThread extends Thread {
            public Handler mHandler;
    
            @Override
            public void run() {
                Looper.prepare();
                mHandler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        if(msg.what == 0) {
                            doLongRunningOperation();
                        }
                    }
                };
                Looper.loop();
            }
    
            private void doLongRunningOperation() {
                // Long Task
            }
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            mLooperThread.mHandler.getLooper().quit();
        }
    }
    


    4.2 메시지 전달에 사용되는 클레스

    • MessageQueue : 무제한의 단방향 연결 리스트, 타임스템프 기준으로 정렬된다. 전달경계에 전달된 대기 메시지에 한에서 실행된다.
    • MessageQueue.IdleHandler : 전달 경계를 넘은 메시지가 없다면 다른 태스크의 실행을 위해 이용할 수 있는 타임 슬롯이 생긴다. 더이상 처리할 메시지가 없으면 콜백을 받아 스레드 종료를 할 수 있다.
      private static class ConsumeAndQuitThread extends Thread implements MessageQueue.IdleHandler {
      
              private static final String THREAD_NAME = "ConsumAndQuitThread";
              public Handler mConsumerHandler;
              private boolean mIsFirstIdle = true;
      
              public ConsumeAndQuitThread() {
                  super(THREAD_NAME);
              }
      
              @Override
              public void run() {
                  Looper.prepare();
                  mConsumerHandler = new Handler() {
                      @Override
                      public void handleMessage(Message msg) {
                          //데이터 소비
                      }
                  };
                  Looper.myQueue().addIdleHandler(this);
                  Looper.loop();
              }
      
              @Override
              public boolean queueIdle() {
                  if (mIsFirstIdle) {
                      mIsFirstIdle = false;
                      return true;
                  }
                  mConsumerHandler.getLooper().quit();
                  return false;
              }
      
              public void enqueueData(int i) {
                  mConsumerHandler.sendEmptyMessage(i);
              }
          }
      


    4.4.3 메시지

    메시지 큐의 각 항목은 android.os.Message 클래스이다.


    데이터 메시지 : 데이터 세트는 소비자 스레드로 옮겨질 수 있는 여러개의 매개변수를 가진다.


    매개변수 이름 

    자료형 

    사용법 

    what 

    int 

    메시지 식별자, 메시지 의도를 전달 

     arg1, arg2

    int 

    정수값을 전달하는 경우 사용되는 간단한 데이터 값 

    obj 

    Object 

    임의의 객체 전달, Parcelable로 구현해서 전달 해야 함 

    data 

    Bundle 

    임의의 데이터값의 컨테이너 

    replyTo 

    Messenger 

    다른 프로세스의 핸들러를 참조, 프로세스 간 통신 가능 

    callback 

    Runnable 

    Handler.post 메서드에서 Runnable객체를 담고 있는 내부  인스턴스 변수 



    태스크 메시지 : 소비자 스레드에서 실행 될 java.lang.Runnable 객체로 표현


    메시지 생명 주기 상태 : 각각의 메시지 생명 주기 상태에 대해 알아보자.


    생성 -(생성)-> 초기화 상태 -(큐에삽입)-> 대기상태 -(전달)-> 전달상태 -(재활용)-> 재활용상태 


    • 초기화 상태
      가변 상태의 메시지 객체가 생성될 수 있음
      // 명시적 객체 생성
      Message m = new Message();
      
      // 팩토리 메서드
      // 데이터 메시지
      Message m = Message.obtain(Handler h, int what, ...);
      
      // 테스크 메시지
      Message m = Message.obtain(Handler h, Runnable task);
      
      // 복사 생성자
      Message m = Message.obtain(Message originalMsg);
      


    • 대기 상태
      메시지가 생산자 스레드에 의해 큐에 삽입되고, 소비자 스레드로 전달되기 위해 기다림

    • 전달상태
      루퍼는 큐에서 메시지를 가져오고 제거 함. (루퍼가 알아서 관리)

    • 재활용 상태
      메시지의 상태가 해제되고 인스턴스가 메시지 풀로 반환 됨

    4.4.4 루퍼
    android.os.Looper 클래스 큐에 있는 메시지를 관련된 핸들러로 발송하는 일을 처리 함.
    UI스레드에서는 기본적으로 하나의 루퍼가 있으며, 루퍼는 스레드에서 생성될 떄 메시지 큐에 결결되고 메시지 큐와 스레드 사이의 중재자 역할을 한다.
    class ConsumerThread extends Thread {
        @Override
        public void run() {
            Looper.prepare();   // 루퍼 생성 - 현재 스레드와 메시지 큐와의 연결 작업
           // 핸들러 생성
           Looper.loop();    // 메시지 큐에서 메시지를 처리하기 시작
        }
    }
    

    루퍼 종료

    루퍼는 quit()이나 quitSafely() 메서드로 메시지 처리 중지 요청을 받음.

    quit : 루퍼가 큐에서 더 이상의 메시지를 전달받는 것을 중지

    quitSafely() : 전달 경계를 넘지 않은 메시지만을 큐에서 폐기


    UI 스레드 루퍼

    기본적으로 루퍼를 소유한 유일한 스레드는 UI스레드다. 

    UI 스레드 루퍼와 다른 응용프로그램 루퍼의 차이점

    • Looper.getMainLooper() 메서드를 이용하면 어디서든 접근 가능
    • 종료시킬 수 없고 Looper.quit() 호출 시 RuntimeException 발생
    • 런타임은 Looper.prepareMainLooper()로 UI스레드에 루퍼를 연결함. 단 한번만 수행하므로, 메인루퍼를 다른스레드에 부착하려 하면 예외 발생

    4.4.5 핸들러
    android.os.Handler 클래스 : 메시지 큐 삽입과 메시지 처리를 다루는 양면성을 가진 API(생산자 - 소비자 스레드 모두에 의해 호출)
    • 메시지 생성
    • 큐에 메시지 삽입
    • 소비자 스레드에서 메시지 처리
    • 큐의 메시지 관리
    설정
    핸들러는 루퍼 메시지 큐 메시지와 상호작용 함. 핸들러는 루퍼 없이는 큐에 메시지를 삽입할 수 없으므로 인스턴스 생성시 루퍼에 바인딩됨.
    // 명시적 루퍼 선언 없이 현재 스레드 루퍼에 바인딩 되는 Handler 생성자
    new Handler();
    new Handler(Handler.Callback);
    
    //명시적으로 루퍼를 선언하고 선언한 루퍼에 바인딩 되는 Handler 생성자
    new Handler(Looper);
    new Handler(Looper, Handler.Callback);
    

    스레드는 여러개의 핸들러를 갖고, 서로 다른 핸들러를 가진 메시지들이 메시지 큐 안에 함께 존재 할 수 있지만, 루퍼를 통해 각자 올바른 Handler 인스턴스로 전달된다.

    메시지 생성

    Handler 클래스는 Message 클래스 객체 생성 시 팩토리 메서드에 대한 래퍼 기능을 제공한다.

    Message obtainMessage(int what, int arg1, int arg2);
    ...
    

    메시지 삽입

    태스크 메서드는 post를 통해 삽입되고, 데이터 메시지는 send를 통해 삽입된다.

    //메시지 큐에 테스크 추가
    boolean post(Runnable r)
    ...
    
    // 메시지 큐에 데이터 객체 추가
    boolean sendMessage(Message msg)
    ...
    
    // 메시지 큐에 간단한 데이터 객체 추가
    boolean sendEmptyMessage(int what)
    ...
    

    예제 : 양방향 메시지 전달

    public class HandlerExampleActivity extends AppCompatActivity {
    
        private final static int SHOW_PROGRESS_BAR = 1;
        private final static int HIDE_PROGRESS_BAR = 0;
        private BackGroundThread mBackgroundThread;
    
        private TextView mText;
        private Button mButton;
        public ProgressBar mProgressBar;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_handler_example);
            mBackgroundThread = new BackGroundThread();
            mBackgroundThread.start();
    
            mText = (TextView) findViewById(R.id.textView);
            mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
            mButton = (Button) findViewById(R.id.button);
            mButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    mBackgroundThread.doWork();
                }
            });
        }
    
        public class BackGroundThread extends Thread {
    
            private Handler mBackGroundHandler;
    
            @Override
            public void run() {
                Looper.prepare();
                mBackGroundHandler = new Handler();
                Looper.loop();
            }
    
            public void doWork() {
                mBackGroundHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Message uiMsg = mUiHandler.obtainMessage(SHOW_PROGRESS_BAR, 0,0,null);
                        mUiHandler.sendMessage(uiMsg);
    
                        Random r = new Random();
                        int randomInt = r.nextInt(5000);
                        SystemClock.sleep(randomInt);
    
                        uiMsg = mUiHandler.obtainMessage(HIDE_PROGRESS_BAR, randomInt, 0 , null);
                        mUiHandler.sendMessage(uiMsg);
                    }
                });
            }
    
            public final Handler mUiHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                        case SHOW_PROGRESS_BAR:
                            mProgressBar.setVisibility(View.VISIBLE);
                            break;
                        case HIDE_PROGRESS_BAR:
                            mProgressBar.setVisibility(View.INVISIBLE);
                            break;
                    }
                }
            };
        }
    }
    
    



    메시지 처리

    루퍼에 의해 전달된 메시지는 소비자 스레드의 핸들러에 의해 처리된다. 메시지 유형에 따라 처리 방식이 다르다.

    • 태스크 메시지 : Handler.handleMessge()호출 없이 자동으로 Runnable run 메서드가 정의된 대로 처리가 실행 된다.
    • 데이터 메시지 : 핸들러가 데이터를 수전하고 그 처리를 담당한다. Hndler.handleMessage(Message msg) 메서드를 오버라이드 하여 데이터를 처리
      class ConsumerThread extends Thread {
          public Handler mHandler;
          @Oberride
          public void run() {
              Looper.prepare();
              mHandler = new Handler() {
                  public void handleMessage(Message msg) {
                      //데이터 처리
                  }
             };
             Looper.loop();
            }
      }
      
      // 손쉬운 방법은 Handler.Callback을 implement 한다.
      public class HandlerCallbackActivity extends Activity implements Handler.Callback {
      
      ..중략
         mUIHandler = new Handler(this);
      
      ....
         @Override
         public boolean handleMessage(Message message) P
             // 메시지 처리
             return true;
          }
      }
      

       
      // 메시지 Callback에서 가로채는 방법 - 1일때 Callback에서 처리하고 차단한다.
      
      public class HandlerCallbackActivity extends AppCompatActivity implements Handler.Callback {
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_handler_callback);
              Button button = (Button) findViewById(R.id.button2);
              button.setOnClickListener(new View.OnClickListener() {
                  @Override
                  public void onClick(View view) {
                      onHandlerCallback();
                  }
              });
          }
      
          @Override
          public boolean handleMessage(Message message) {
              switch (message.what) {
                  case 1 :
                      message.what = 11;
                      return true;
                  default :
                      message.what = 22;
                      return false;
              }
          }
      
          public void onHandlerCallback() {
              Handler handler = new Handler(this) {
                  @Override
                  public void handleMessage(Message msg) {
                      Log.e("SIM", "msg:" + msg.what);
                  }
              };
              handler.sendEmptyMessage(1);
              handler.sendEmptyMessage(2);
          }
      }
      

    4.4.6 큐에서 메시지 제거
    // 메시지 큐에서 태스크를 제거
    removeCallbacks(Runnable r);
    removeCallbacks(Runnable r, Object token);
    
    // 메시지 큐에서 데이터 메시지를 제거
    removeMessage(int what)
    removeMessage(int what, Object object)
    
    // 메시지 큐에서 태스크와 데이터 메시지를 제거한다.
    removeCallbackAndMessage(Object token);
    


    4.4.7 메시지 큐 관찰

    //현재 메시지 큐의 스냅숏 찍기 (로그를 이용하여 message 전달 과정을 찍음)
    mWorkerHandler.dump(new LogPrinter(Log.DEBUG, TAG), "" );
    
    //메시지 큐의 처리를 추적
    Lopper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, TAG));
    

    4.5 UI 스레드와 통신

    UI스레드가 자신에게 보낸 태스크 메시지는 메시지 전달을 건너뛰고 UI스레드에서 현재 처리된 메시지 내에서 바로 실행할 수 있다. Activity.runOnUiThread(Runnable) 메서드를 이용한다.
    // UI 스레드로 호출한 메서드
    private void postFromUIThreadToUiThread() {
        new Handler().post(new Runnable() { ... } );
    }
    
    // 다음이 더 쉽다.
    private void postFromUiThreadToUiThread() {
        runOnUiThread(new Runnable() { .... } );
    }
    

    UI스레드 외부에서 호출되면 메시지는 큐에 삽입 되는 예제

    public class EatApplication extends AppCompatActivity {
    
        private long mUiThreadId;
        private Handler mUiHandler;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_eat_application);
            mUiThreadId = Thread.currentThread().getId();
            mUiHandler = new Handler();
        }
    
        public void customRunOnUiThread(Runnable action) {
            if (Thread.currentThread().getId() != mUiThreadId) {
                mUiHandler.post(action);
            } else {
                action.run();
            }
        }
    }
    

    4.6 마치며

    지금까지 하나의 프로세스 안에서 여러개의 스레드 통신 기업에 대해서 설명하였다. 긴 설명이었지만 그만큼 안드로이드 멀티 스레딩 프로그램에 있어서 스레드간 통신 기법은 매우 중요하다. 이 원리를 이해하고 안드로이드에서 제공하는 여러가지 래핑 기법을 통해 성능 좋은 application을 짤 수 있다. 


    다음 시간에는 안드로이드 프로세스간 통신에 대해서 알아보도록 하자

    'Android > Android Multi Threading' 카테고리의 다른 글

    6. 메모리 관리  (0) 2016.07.29
    5. 프로세스 간 통신  (0) 2016.07.28
    3. 안드로이드 스레드  (0) 2016.07.25
    2. 자바의 멀티 스레딩  (0) 2016.07.20
    안드로이드 멀티스레딩 정리 개요  (0) 2016.07.20
    댓글
    댓글쓰기 폼
    공지사항
    최근에 달린 댓글
    Total
    66,013
    Today
    0
    Yesterday
    10
    TAG
    more
    «   2022/12   »
    1 2 3
    4 5 6 7 8 9 10
    11 12 13 14 15 16 17
    18 19 20 21 22 23 24
    25 26 27 28 29 30 31
    글 보관함