| 표지 설명 | 표지에 있는 동물은 돌핀피시라고도 부르는 만새기mahi-mahi 다. 이 조 기어류의 물고기는 온대, 열대, 아열대 바다에 서식하는 표서자 다. 만새기의 이름 마히마히는 하와이어로 ‘매우 강력한’이라 는 뜻이다. 돌핀피시dolphinfish 라는 별칭이 있지만, 돌고래와는 관련 이 없다. 만새기속은 만새기와 줄만새기 두 가지 종이 있다. 남아프 리카공화국의 영어권 해안선을 따라, 이 물고기는 종종 스페인어 이름인 도라도Dorado 라고도 불린다. 만새기는 7~13킬로그램까지 성장하며, 드물게 15킬로그램을 넘기도 한다. 최장 5년까지 살 수 있지만, 평균 4년 정도 생 존한다. 만새기의 납작한 몸통에는 거의 전체 몸길이에 달하는 긴 등지느러미가 있다. 만새기는 화려한 색상이 특징이다. 옆구리와 등에 밝은 청색과 녹색이 섞인 폭이 넓은 황금색 측선이 있고, 삼면에 검은 줄무늬 사선이 있다.
안드로이드 멀티스레딩 : 비동기 메커니즘으로 날렵하고 안정적인 앱 만들기 초판발행 2015년 3월 1일 지은이 안데르스 예란손 / 옮긴이 한대희 / 펴낸이 김태헌 펴낸곳 한빛미디어 (주) / 주소 서울시 마포구 양화로 7길 83 한빛미디어(주) IT출판부 전화 02 – 325 – 5544 / 팩스 02 – 336 – 7124 등록 1999년 6월 24일 제10 – 1779호 / ISBN 978 – 89 – 6848 – 170 – 3
93000
총괄 배용석 / 책임편집 최현우 / 기획 이상복 / 편집 김철수 디자인 표지 손경선, 내지 여동일 영업 김형진, 김진불, 조유미 / 마케팅 박상용, 서은옥 / 제작 박성우 이 책에 대한 의견이나 오탈자 및 잘못된 내용에 대한 수정 정보는 한빛미디어(주)의 홈페이지나 아래 이메일로 알려주십시오. 잘못된 책은 구입하신 서점에서 교환해드립니다. 책값은 뒤표지에 표시되어 있습니다. 한빛미디어 홈페이지 www.hanbit.co.kr / 이메일 ask@hanbit.co.kr
Published by HANBIT Media, Inc. Printed in Korea Copyright © 2015 O’Reilly Media & HANBIT Media, Inc. Authorized translation of the English edition of Efficient Android Threading, ISBN 9781449364137 ©2014 Anders Göransson. This translation is published and sold by permission of O’Reilly Media, Inc., whitch owns or controls all rights to publish and sell the same. 이 책의 저작권은 오라일리와 한빛미디어 (주)에 있습니다. 저작권법에 의해 보호를 받는 저작물이므로 무단 복제 및 무단 전재를 금합니다.
지금 하지 않으면 할 수 없는 일이 있습니다. 책으로 펴내고 싶은 아이디어나 원고를 메일 ( writer@hanbit.co.kr ) 로 보내주세요. 한빛미디어(주)는 여러분의 소중한 경험과 지식을 기다리고 있습니다.
옮긴이의 말
“안드로이드 프로그래밍의 기본을 알고 나면 다음엔 무엇을 알아야 하나요?” “안드로이드 개발은 하면 할수록 어려운 것 같아요!” 보통 안드로이드 프로그래밍을 학습하는 개발자라면, 간단히 기본서를 읽고 예제 코드를 만들어 보면서 프로그래밍을 배워갑니다. 이렇게 안드로이드 관련 지식이 쌓이고 프로젝트도 경험해보 면서 점점 안드로이드 프로그래밍의 즐거움을 느낄 수 있습니다. 그러나 실무에서 진행하는 프로젝트를 하거나 복잡도가 높은 요구사항을 구현하다 보면 책에서 설명해주지 않은 어려움을 많이 접하게 됩니다. 그중 가장 습득하기 어려운 것이 성능 향상을 위 한 안드로이드의 스레드 처리일 것입니다. 역자도 동일한 어려움을 겪고 있을 때, 바로 이 책 『Efficient Android Threading』을 접하게 되었습니다. 이 책은 안드로이드의 비동기 처리 클래스뿐만 아니라 하부에 자리 잡은 자바의
Thread와 Concurrent 패키지에 대한 처리 방식까지, 많은 영역을 이해하는 데 아주 큰 도움 을 주었습니다. 안드로이드의 스레드 처리에 대해서 알고 싶은 개발자라면 누구나 읽어도 도움이 되겠지만, 특 히 안드로이드 스레드 처리의 이론적인 기본 원리를 이해하고 실제 업무에 적용해보려는 개발자 에게 이 책을 추천합니다. 개발자로서 실력을 키우는 방법은 언제나 모르는 것에 궁금증을 갖고 집요할 정도로 이해할 때 까지 파고드는 능력에 있다고 봅니다. 이 책은 최대한 많은 부분을 기술하려 했지만 주제에서 벗 어나거나 다뤄야 하는 내용이 많기에 간단히 언급만 하고 넘어간 것으로 보이는 부분이 있습니 다. 안드로이드 관련 주제에 대한 자세한 정보는 안드로이드 개발자 가이드를 참고하면 도움이 될 것입니다. 번역을 마치기까지 감사를 드려야 할 분이 너무도 많습니다. 번역을 시작할 수 있게 도와주신 한빛미디어의 최현우 팀장님, 책의 내용을 함께 검토하고 처음부터 끝까지 마무리할 수 있게 도와주신 이상복 대리님, 함께 일하면서 궁금한 질문에 대해 언제나 웃으면서 설명해주시고 개
4
발자로서의 자세에 귀감이 된 Justin Song, 먼 미국에 있는 친동생을 위해 늘 기도해주는 한 광희 목사님께 감사를 드립니다. 마지막으로 임신과 출산 중에도 늦은 밤까지 번역하는 남편을 늘 격려해주고 지지해준 사랑하는 아내와 삶의 이유가 된 석 달배기 우리 규현이에게 감사를 전 합니다. 한대희
5
지은이·옮긴이 소개
지은이
안데르스 예란손 Anders Göransson
소프트웨어 아키텍트, 개발자, 교육자다. 엔지니어링 물리 공학박사며 일생을 소프트웨어 산업계에 서 경력을 쌓고 있다. 2001년부터 산업자동화 시스템 분야에서 활동하다 2005년 모바일 기기에 뛰 어들었다. 모바일 플랫폼으로서의 안드로이드 OS의 무궁한 가능성을 내다보았으며, 2008년 스마트 폰 시대가 열린 이래 제조사, 금융기관, 스타트업 등에 도움을 주고 있다.
옮긴이
한대희 @daniel_booknara
TmaxCore, Infraware에서 직장생활을 하였으나 배움의 부족함을 느끼고 서른 살에 카이스트 전 산학과 석사과정에 입학하였다. 졸업과 동시에 미국으로 건너가 안드로이드 개발을 시작했으며 현재 는 실리콘밸리에 있는 ZeroDesktop에서 근무하고 있다. 하루하루 ‘인생도처유상수人生到處有上手’를 되 새기며 즐겁게 개발하고 있다.
6
이 책에 대하여
이 책은 견고하고 신뢰할 수 있는 안드로이드 멀티스레드 응용프로그램을 만드는 방법을 설명한 다. 안드로이드 SDK에서 사용할 수 있는 비동기 메커니즘을 살펴보고, 빠르고 반응성 좋으며 잘 구조화된 응용프로그램을 만드는 데 적합한 구현 방법을 알아볼 것이다. 불편하지만 알아야 하는 것으로, 멀티스레딩은 사용자 경험이 뛰어난 응용프로그램을 만드는 데 필요하지만, 응용프로그램의 복잡성과 런타임 에러의 가능성을 증가시킨다. 복잡성은 부분적으 로 여러 스레드 실행의 어려움뿐만 아니라 안드로이드 플랫폼을 효율적으로 활용하지 않는 응용 프로그램에서도 비롯된다. 이 책의 목적은 비동기 메커니즘의 장점과 어려움을 이해함으로써 응용프로그램 개발자가 적절 한 비동기 메커니즘을 선택하도록 도와주려는 데 있다. 적절한 비동기 메커니즘을 사용함으로써 다수의 복잡성을 응용프로그램에서 플랫폼으로 이동시키며, 응용프로그램 코드를 유지 보수하 기 쉽게 만들고 에러 발생률을 줄일 수 있다. 경험에 비추어볼 때, 비동기 실행은 필요 이상으로 코드에 더 많은 복잡성을 초래해서는 안 된다. 그러기 위해서는 안드로이드의 다양한 비동기 메 커니즘 중에서 가장 적합한 메커니즘을 선택해야 한다. 높은 수준의 비동기 메커니즘은 매우 편리하게 사용될 수 있지만, 사용자는 여전히 비동기 메커 니즘을 완전히 이해하고 사용할 필요가 있다. 그렇지 않으면 응용프로그램은 런타임 오류, 성능 저하, 메모리 누수와 같은 어려움을 겪을 수 있다. 그러므로 이 책은 실제 지침과 예제뿐만 아 니라 안드로이드에서 비동기 실행을 위한 기본 작동 코드를 살펴본다.
대상 독자 이 책은 안드로이드 프로그래밍의 기본을 익힌 자바 프로그래머를 대상으로 한다. 표준 안드로 이드 라이브러리를 사용하여 견고하고 반응성 있는 응용프로그램을 작성하는 데 필요한 기본 기 술을 소개한다.
7
이 책의 구성 이 책은 1부와 2부로 구성되어 있다. 1부에서는 안드로이드에서 스레드의 기초(자바, 리눅스, 핸들러)와 스레드가 응용프로그램에 미치는 영향을 설명한다. 2부에서는 응용프로그램에서 사 용되는 비동기 메커니즘의 처리를 살펴보고, 실제 비동기 메커니즘을 직접 경험해본다.
1부에서는 자바가 어떻게 스레드를 처리하는지 설명한다. 안드로이드 개발자라면 여기서 다루는 라이브러리들을 직접 사용할 일이 있을 것이다. 이들의 동작을 이해해야 2부에서 다루는 고수준 구성요소들을 제대로 사용할 수 있다. 1장_ 안드로이드 응용프로그램의 다양한 구성요소와 안드로이드 런타임 구조가 어떻게 스레드
와 멀티프로세싱의 사용에 영향을 미치는지 설명한다. 2장_ 자바에서 동시 실행의 기본을 다룬다. 3장_ 안드로이드가 스레드를 처리하는 방법과 응용프로그램의 스레드가 리눅스 OS에서 실행되
는 방법을 설명한다. 여기에는 스케줄링과 컨트롤 그룹control group뿐만 아니라 응답에 미치는 영 향과 같은 중요한 주제도 포함한다. 4장_ 스레드 간의 기본 통신 수단으로 공유 메모리shared memory, 시그널signal, 그리고 일반적으로
사용하는 안드로이드 메시지를 다룬다. 5장_ RPC, 메시징 등과 같은 메커니즘으로 안드로이드가 리눅스의 IPC 메커니즘을 향상시키는
방법을 보여준다. 6장_ 시스템을 저하시키고 사용자가 응용프로그램을 삭제하게 만드는 원인이 되는 누수를 피하
는 방법을 설명한다.
8
2부에서는 안드로이드에서 스레드를 사용하여 더욱 안전하고 쉽게 프로그래밍할 수 있게 해주 는 높은 수준의 라이브러리와 구문을 다룬다. 7장_ 가장 기본인 비동기 구문( java.lang.Thread )을 설명하고, 스레드의 다양한 상태와 발생
할 수 있는 문제를 처리하는 방법을 살펴본다. 8장_ 백그라운드에서 태스크를 순차적으로 실행하는 편리한 방법을 살펴본다. 9장_ 스케줄링, 에러, 그리고 스레드 풀과 같은 스레드 처리의 다른 측면을 다루는 기술을 제공
한다. 10장_ 가장 많이 사용하는 비동기 기법인 AsyncTask를 살펴보고, 이 기법의 위험을 피해 올바
르게 사용하는 방법을 다룬다. 11장_ 여러 응용프로그램에 제공되는 기능이나 백그라운드 실행을 하는 동안 응용프로그램이
살아 있도록 하는 데 유용하게 쓰이는 필수 서비스 구성요소를 설명한다. 12장_ 이전 장을 기반으로, 기본 UI 스레드를 실행하기 위한 유용한 기술에 대한 논의를 다룬다. 13장_ 콘텐트 프로바이더content provider 에 대한 빠른 비동기식 접근을 간소화하는 높은 수준의 메커
니즘을 다룬다. 14장_ 변경된 데이터가 전달될 때마다 비동기식으로 새로운 데이터가 전달되는 로더를 통해 UI
를 업데이트하는 방법을 알아본다. 15장_ 이 책에서 기술된 모든 기법을 고려해볼 때, 어떻게 응용프로그램에 알맞은 기법을 선택
할 수 있을까? 이 장에서는 이러한 선택을 위한 지침을 제공한다.
9
감사의 글
책을 쓰는 일은 외로운 작업으로 여겨지곤 한다. 하지만 그건 어쩔 수 없이 잠자리에 들기 전에 마지막 단락을 마치고자 애쓰는 늦은 밤의 시간에만 해당하는 얘기다. 실제로 집필은 책을 가능 하게 해준 여러 사람들로 둘러싸여 있다. 우선 내게 책을 쓸 것을 제안하고 집필 과정의 초기 단계를 도와준 오라일리의 레이철 루멜리 오티스Rachel Roumeliotis 에게 감사의 말씀을 전한다. 사실 이 책을 쓰면서 함께 작업했던 오라일리의 모든 분이 높은 전문성을 보여주었고 내게 도움을 주었다. 덕분에 집필에 집중하는 것이 수월할 수 있었다. 특히 이 책을 현실로 만드는 데 중요한 역할을 한 편집자 앤디 오럼Andy Oram 에게 감사 의 말을 전한다. 그는 이 프로젝트의 초고에 이의를 제기하고, 귀중한 피드백을 제공하면서 참을 성 있게 나와 작업해주었다. 복잡한 소프트웨어를 개발하는 것과 마찬가지로, 책 집필은 도중에 많은 버그를 포함한다. 또 한 각 장마다 최종 출시 전에 버그 수정 및 안정화 기간을 거친다. 나는 테크니컬 리뷰어인 제프 식스Jeff Six 와 이언 다윈Ian Darwin 에게서 초고의 문제점을 정확히 파악하는 데 큰 도움을 받았다. 그들은 쉼표를 빼먹은 것부터 코딩 에러와 구조 개선까지 다수의 의견을 제공해주었다. 정말 고 맙습니다! 이 책은 가족의 도움 없이는 쓸 수 없었을 것이다. 늦은 밤까지 일하는 것을 참아준 데 대해 감사 한다. 솔직히 가족이 이 책을 읽지는 않으리라는 것을 안다. 그럼에도 이 책이 우리 가족 책장의 일부가 될 것을 희망한다. 안데르스 예란손
10
CONTENTS
옮긴이의 말 �������������������������������������������������������������������������������������������������������������������� 4
지은이·옮긴이 소개 �������������������������������������������������������������������������������������������������������� 6
이 책에 대하여 ���������������������������������������������������������������������������������������������������������������� 7
감사의 글 ��������������������������������������������������������������������������������������������������������������������� 10
CHAPTER
1 안드로이드 구성요소와 멀티프로세싱의 필요성
1.1 안드로이드 소프트웨어 스택 ������������������������������������������������������������������������������������������ 19
1.2 응용프로그램 아키텍처 ������������������������������������������������������������������������������������������������� 21
1.2.1 Application 객체 �������������������������������������������������������������������������������������������� 21
1.2.2 구성요소 �������������������������������������������������������������������������������������������������������� 21
1.3 응용프로그램 실행 ������������������������������������������������������������������������������������������������������� 24
1.3.1 리눅스 프로세스 ���������������������������������������������������������������������������������������������� 25
1.3.2 생명주기 �������������������������������������������������������������������������������������������������������� 25
1.4 성능을 위해 구조화된 응용프로그램 �������������������������������������������������������������������������������� 29
1.5 마치며 ���������������������������������������������������������������������������������������������������������������������� 31
Part
I
CHAPTER
1.4.1 스레드를 통해 반응성 있는 응용프로그램 만들기 ���������������������������������������������������� 29
기초
2 자바의 멀티스레딩 2.1 스레드의 기본 ������������������������������������������������������������������������������������������������������������ 35
2.1.1 실행 �������������������������������������������������������������������������������������������������������������� 36
2.1.2 싱글스레드 응용프로그램 ���������������������������������������������������������������������������������� 37
2.1.3 멀티스레드 응용프로그램 ���������������������������������������������������������������������������������� 38
11
CONTENTS
2.2 스레드 안전 ��������������������������������������������������������������������������������������������������������������� 40
2.2.1 암시적 잠금과 자바 모니터 �������������������������������������������������������������������������������� 41
2.2.2 공유 자원 접근의 동기화 ����������������������������������������������������������������������������������� 43
2.2.3 예제: 소비자와 생산자 �������������������������������������������������������������������������������������� 46
2.3 태스크 실행 전략 ��������������������������������������������������������������������������������������������������������� 48
CHAPTER
2.3.1 동시 실행 설계 ������������������������������������������������������������������������������������������������ 49
2.4 마치며 ���������������������������������������������������������������������������������������������������������������������� 49
3 안드로이드 스레드 3.1 안드로이드 응용프로그램 스레드 ������������������������������������������������������������������������������������ 51
3.1.1 UI 스레드 ������������������������������������������������������������������������������������������������������� 52
3.1.2 바인더 스레드 ������������������������������������������������������������������������������������������������� 52
3.1.3 백그라운드 스레드 ������������������������������������������������������������������������������������������� 52
3.2 리눅스 프로세스와 스레드 ��������������������������������������������������������������������������������������������� 53
CHAPTER
3.2.1 스케줄링 �������������������������������������������������������������������������������������������������������� 57
3.3 마치며 ���������������������������������������������������������������������������������������������������������������������� 61
4 스레드 통신 4.1 파이프 ���������������������������������������������������������������������������������������������������������������������� 63
4.1.1 기본 파이프 사용 ��������������������������������������������������������������������������������������������� 65
4.1.2 예제: 작업자 스레드에서 문자 처리 ��������������������������������������������������������������������� 66
4.2 공유 메모리 ��������������������������������������������������������������������������������������������������������������� 69
4.2.1 시그널링 �������������������������������������������������������������������������������������������������������� 70
4.3 블로킹 큐 ������������������������������������������������������������������������������������������������������������������ 71
4.4 안드로이드 메시지 전달 ������������������������������������������������������������������������������������������������ 73
12
4.4.1 예제: 기본 메시지 전달 ������������������������������������������������������������������������������������� 75
4.4.2 메시지 전달에 사용되는 클래스 �������������������������������������������������������������������������� 77
4.4.3 메시지 ����������������������������������������������������������������������������������������������������������� 82
4.4.4 루퍼 �������������������������������������������������������������������������������������������������������������� 85
4.4.5 핸들러 ����������������������������������������������������������������������������������������������������������� 87
4.4.6 큐에서 메시지 제거 ������������������������������������������������������������������������������������������ 96
4.4.7 메시지 큐 관찰 ������������������������������������������������������������������������������������������������ 98
4.5 UI 스레드와 통신 ������������������������������������������������������������������������������������������������������ 102
4.6 마치며 �������������������������������������������������������������������������������������������������������������������� 104
CHAPTER
5 프로세스 간 통신 5.1 안드로이드 RPC ������������������������������������������������������������������������������������������������������ 105
5.1.1 바인더 ��������������������������������������������������������������������������������������������������������� 106
5.2 AIDL ���������������������������������������������������������������������������������������������������������������������� 108
5.2.1 동기식 RPC ������������������������������������������������������������������������������������������������� 109
5.2.2 비동기식 RPC ���������������������������������������������������������������������������������������������� 112
5.3 바인더를 이용한 메시지 전달 ��������������������������������������������������������������������������������������� 115
5.3.1 단방향 통신 �������������������������������������������������������������������������������������������������� 116
5.3.2 양방향 통신 �������������������������������������������������������������������������������������������������� 119
CHAPTER
5.4 마치며 �������������������������������������������������������������������������������������������������������������������� 120
6 메모리 관리
6.1 가비지 컬렉션 ���������������������������������������������������������������������������������������������������������� 121
6.2 스레드 관련 메모리 누수 ��������������������������������������������������������������������������������������������� 123
6.2.1 스레드 실행 �������������������������������������������������������������������������������������������������� 124
13
CONTENTS
6.2.2 스레드 통신 �������������������������������������������������������������������������������������������������� 131
6.3 메모리 누수 방지 ������������������������������������������������������������������������������������������������������� 134
6.3.1 정적 내부 클래스 사용 ������������������������������������������������������������������������������������ 134
6.3.2 약한 참조 사용 ���������������������������������������������������������������������������������������������� 135
6.3.3 작업자 스레드 실행 중지 ��������������������������������������������������������������������������������� 135
6.3.4 작업자 스레드 유지 ���������������������������������������������������������������������������������������� 136
6.3.5 메시지 큐 정리 ���������������������������������������������������������������������������������������������� 136
6.4 마치며 �������������������������������������������������������������������������������������������������������������������� 137
Part
II
CHAPTER
비동기 기법
7 기본 스레드의 생명주기 관리 7.1 기본사항 ����������������������������������������������������������������������������������������������������������������� 141
7.1.1 생명주기 ������������������������������������������������������������������������������������������������������ 141
7.1.2 인터럽트 ������������������������������������������������������������������������������������������������������ 143
7.1.3 잡히지 않은 예외 ������������������������������������������������������������������������������������������� 145
7.2 스레드 관리 ������������������������������������������������������������������������������������������������������������� 147
7.2.1 정의와 시작 �������������������������������������������������������������������������������������������������� 147
7.2.2 유지 ������������������������������������������������������������������������������������������������������������ 149
CHAPTER
7.3 마치며 �������������������������������������������������������������������������������������������������������������������� 155
8 핸들러 스레드: 고수준 큐 메커니즘
8.1 기본사항 ����������������������������������������������������������������������������������������������������������������� 157
8.2 생명주기 ����������������������������������������������������������������������������������������������������������������� 159
14
8.3 사용 사례 ���������������������������������������������������������������������������������������������������������������� 161
8.3.1 반복되는 태스크 실행 ������������������������������������������������������������������������������������� 161
8.3.2 관련 태스크 �������������������������������������������������������������������������������������������������� 162
8.3.3 태스크 연쇄 �������������������������������������������������������������������������������������������������� 165
8.3.4 조건부 태스크 삽입 ���������������������������������������������������������������������������������������� 168
CHAPTER
8.4 마치며 �������������������������������������������������������������������������������������������������������������������� 169
9 Executor 프레임워크를 통한 스레드 실행 제어
9.1 Executor ���������������������������������������������������������������������������������������������������������������� 171
9.2 스레드 풀 ���������������������������������������������������������������������������������������������������������������� 174
9.2.1 미리 정의된 스레드 풀 ������������������������������������������������������������������������������������ 175
9.2.2 커스텀 스레드 풀 ������������������������������������������������������������������������������������������� 176
9.2.3 스레드 풀 설계 ���������������������������������������������������������������������������������������������� 177
9.2.4 생명주기 ������������������������������������������������������������������������������������������������������ 182
9.2.5 스레드 풀의 중단 ������������������������������������������������������������������������������������������� 183
9.2.6 스레드 풀 사용 사례와 위험성 �������������������������������������������������������������������������� 185
9.3 태스크 관리 ������������������������������������������������������������������������������������������������������������� 186
9.3.1 태스크 표현 �������������������������������������������������������������������������������������������������� 186
9.3.2 태스크 보내기 ����������������������������������������������������������������������������������������������� 188
9.3.3 태스크 거부하기 �������������������������������������������������������������������������������������������� 192
9.4 ExecutorCompletionService ���������������������������������������������������������������������������������� 193
9.5 마치며 �������������������������������������������������������������������������������������������������������������������� 196
CHAPTER
10 AsyncTask로 백그라운드 태스크를 UI 스레드에 묶기 10.1 기본사항 ����������������������������������������������������������������������������������������������������������������� 199
15
CONTENTS
10.1.1 생성과 시작 �������������������������������������������������������������������������������������������������� 202
10.1.2 취소 ������������������������������������������������������������������������������������������������������������ 203
10.1.3 상태 ������������������������������������������������������������������������������������������������������������ 205
10.2 AsyncTask 구현 ������������������������������������������������������������������������������������������������������ 206
10.2.1 예제: 이미지 다운로드 ������������������������������������������������������������������������������������ 207
10.3 백그라운드 태스크 실행 ���������������������������������������������������������������������������������������������� 210
10.3.1 응용프로그램 전역 실행 ���������������������������������������������������������������������������������� 212
10.3.2 다양한 플랫폼 버전에서 실행 ��������������������������������������������������������������������������� 214
10.3.3 커스텀 실행 �������������������������������������������������������������������������������������������������� 216
10.4 AsyncTask의 대안 ��������������������������������������������������������������������������������������������������� 217
10.4.1 AsyncTask가 너무 평범하게 구현된 경우 ���������������������������������������������������������� 218
10.4.2 루퍼가 필요한 백그라운드 태스크 ��������������������������������������������������������������������� 218
10.4.3 지역 서비스 �������������������������������������������������������������������������������������������������� 219
10.4.4 execute(Runnable) 사용 ������������������������������������������������������������������������������� 219
CHAPTER
10.5 마치며 �������������������������������������������������������������������������������������������������������������������� 220
11 서비스
11.1 비동기 실행을 위해 서비스를 사용해야 하는 이유 ������������������������������������������������������������ 221
11.2 지역, 원격, 전역 서비스 ���������������������������������������������������������������������������������������������� 223
11.3 생성과 실행 ������������������������������������������������������������������������������������������������������������� 225
11.4 생명주기 ����������������������������������������������������������������������������������������������������������������� 226
11.5 시작 서비스 ������������������������������������������������������������������������������������������������������������� 228
11.5.1 onStartCommand 구현 ������������������������������������������������������������������������������� 229
11.5.2 재시작을 위한 옵션 ���������������������������������������������������������������������������������������� 230
11.5.3 사용자 제어 서비스 ���������������������������������������������������������������������������������������� 232
11.5.4 태스크 제어 서비스 ���������������������������������������������������������������������������������������� 236
16
11.6 바운드 서비스 ���������������������������������������������������������������������������������������������������������� 239
11.6.1 지역 바인딩 �������������������������������������������������������������������������������������������������� 241
11.7 비동기 기술 선정 ������������������������������������������������������������������������������������������������������� 245
11.8 마치며 �������������������������������������������������������������������������������������������������������������������� 245
CHAPTER
12 인텐트 서비스
12.1 기본사항 ����������������������������������������������������������������������������������������������������������������� 247
12.2 인텐트 서비스를 사용하는 좋은 방법 ����������������������������������������������������������������������������� 249
12.2.1 순차적으로 정렬된 태스크 ������������������������������������������������������������������������������� 249
12.2.2 브로드캐스트 리시버에서 비동기 실행 ��������������������������������������������������������������� 253
12.3 인텐트 서비스와 서비스 ���������������������������������������������������������������������������������������������� 256
12.4 마치며 �������������������������������������������������������������������������������������������������������������������� 257
CHAPTER
13 AsyncQueryHandler를 이용한 콘텐트 프로바이더 접근
13.1 콘텐트 프로바이더에 대한 간략한 소개 �������������������������������������������������������������������������� 259
13.2 콘텐트 프로바이더의 백그라운드 처리에 대한 정당성 ������������������������������������������������������� 261
13.3 AsyncQueryHandler 사용 ���������������������������������������������������������������������������������������� 263
13.3.1 예제: 연락처 확장 리스트 �������������������������������������������������������������������������������� 265
13.3.2 AsyncQueryHandler 이해 ���������������������������������������������������������������������������� 268
13.3.3 한계 ������������������������������������������������������������������������������������������������������������ 269
CHAPTER
13.4 마치며 �������������������������������������������������������������������������������������������������������������������� 270
14 로더를 이용한 자동 백그라운드 실행 14.1 로더 프레임워크 �������������������������������������������������������������������������������������������������������� 272
14.1.1 LoaderManager ������������������������������������������������������������������������������������������ 273
17
CONTENTS
14.1.2 LoaderCallbacks ���������������������������������������������������������������������������������������� 276
14.1.3 AsyncTaskLoader ��������������������������������������������������������������������������������������� 279
14.2 CursorLoader를 이용한 쉬운 데이터 로딩 ������������������������������������������������������������������� 280
14.2.1 CursorLoader 사용하기 �������������������������������������������������������������������������������� 280
14.2.2 예제: 연락처 리스트 ��������������������������������������������������������������������������������������� 281
14.2.3 CRUD 지원 추가 ������������������������������������������������������������������������������������������ 282
14.3 커스텀 로더 구현 ������������������������������������������������������������������������������������������������������� 287
14.3.1 로더 생명주기 ����������������������������������������������������������������������������������������������� 287
14.3.2 백그라운드 로딩 �������������������������������������������������������������������������������������������� 289
14.3.3 콘텐츠 관리 �������������������������������������������������������������������������������������������������� 291
14.3.4 캐시된 결과를 전달 ���������������������������������������������������������������������������������������� 293
14.3.5 예제: 커스텀 파일 로더 ����������������������������������������������������������������������������������� 293
14.3.6 여러 개의 로더 처리 ��������������������������������������������������������������������������������������� 297
CHAPTER
14.4 마치며 �������������������������������������������������������������������������������������������������������������������� 298
15 비동기 기술의 선택
15.1 간단하게 하라 ���������������������������������������������������������������������������������������������������������� 300
15.2 스레드와 자원 관리 ���������������������������������������������������������������������������������������������������� 301
15.3 반응성을 위한 메시지 전달 ������������������������������������������������������������������������������������������ 301
15.4 예상치 못한 태스크 종료를 피하라 �������������������������������������������������������������������������������� 302
15.5 콘텐트 프로바이더에 쉽게 접근 ������������������������������������������������������������������������������������ 303
찾아보기 ���������������������������������������������������������������������������������������������������������������������������������������������������
18
305
CHAPTER
1
안드로이드 구성요소와 멀티프로세싱의 필요성
스레드의 세계를 탐구하기에 앞서 안드로이드 플랫폼, 응용프로그램 아키텍처, 응용프로그램의 실행부터 살펴보자. 이 장은 이 책에서 다룰 스레드에 대한 실질적 토론을 위한 지식의 기준점 을 제공하지만, 안드로이드 플랫폼의 자세한 내용은 공식 문서(https://developer.android.
com ) 또는 시중에 나와 있는 다수의 안드로이드 프로그래밍 책에서 찾아볼 수 있다.
1.1 안드로이드 소프트웨어 스택 응용프로그램은 리눅스 커널, 네이티브 C++ 라이브러리, 그리고 응용프로그램 코드를 실행하 는 런타임을 기반으로 하는 소프트웨어 스택 위에서 동작한다(그림 1-1 ). 그림 1-1 안드로이드 소프트웨어 스택
응용프로그램 계층 애플리케이션 프레임워크 네이티브 라이브러리
코어 자바 런타임
리눅스 커널
1장 - 안드로이드 구성요소와 멀티프로세싱의 필요성
19
안드로이드 소프트웨어 스택의 주요한 빌딩 블록building block 으로는 다음과 같은 것들이 있다. | 응용프로그램 | 자바로 구현된 안드로이드 응용프로그램이다. 응용프로그램은 자바와 안드로이드 프레임워크 라이브러리를 사용한다. | 코어 자바 | 응용프로그램과 안드로이드 프레임워크에서 사용된 코어 자바 라이브러리Core Java library다. 코어 자바는 완벽한 자바 SE나 ME 구현을 따르지 않고 자바 5 기반의 중단된 아파치 하모니Apache Harmony
(http://harmony.apache.org )의 부분집합이다. 코어 자바는 기초 자바 스레딩 메커니
즘인 java.lang.Thread 클래스와 java.util.concurrent 패키지를 제공한다. | 애플리케이션 프레임워크 | 윈도우 시스템, UI 툴킷toolkit, 자원 등 기본적으로 자바에서 안드로이드 응용프로그램을 작성하 기 위해 요구되는 모든 것을 다루는 안드로이드 클래스다. 프레임워크는 안드로이드 구성요소 의 생명주기lifecycle 와 구성요소 간의 상호 통신을 정의하고 관리하며 응용프로그램이 스레드 관 리를 단순화하기 위해 활용하는 안드로이드용 비동기 메커니즘(HandlerThread, AsyncTask, IntentService, AsyncQueryHandler, Loaders )의 집합을 정의한다. 이 모든 메커니즘을 책에
서 설명할 것이다. | 네이티브 라이브러리 | 그래픽, 미디어, 데이터베이스, 폰트, OpenGL 등을 다루는 C/C++ 라이브러리다. 안드로이드 프레임워크는 네이티브 코드를 위한 자바 래퍼wrapper 를 제공하기 때문에 자바 응용프로그램은 기본적으로 네이티브 라이브러리와 직접 상호작용을 하지 않는다. | 런타임 | 내부 바이트 코드 표현representation 으로 가상 머신에서 컴파일된 안드로이드 응용프로그램 코드 를 실행하는 샌드박스sandbox 화된 런타임 환경이다. 모든 응용프로그램은 자신만의 달빅Dalvik 또 는 아트Android Runtime (ART ) 런타임에서 동작한다. 아트는 킷캣KitKat (API 레벨 19 )부터 사용자가 활성화할 수 있는 선택적 런타임이며, 이 책을 쓰는 시점에서는 달빅이 기본 런타임이다.
20
1장 - 안드로이드 구성요소와 멀티프로세싱의 필요성
| 리눅스 커널 | 응용프로그램에서 소리, 네트워크, 카메라 등 기기device 의 하드웨어 기능을 사용하기 위한 하부 운영체제며, 프로세스와 스레드도 관리한다. 모든 응용프로그램은 자신만의 프로세스를 시작하 고, 모든 프로세스는 실행되는 응용프로그램과 함께 런타임을 가진다. 프로세스 내의 여러 스레 드가 응용프로그램 코드를 실행할 수 있다. 커널은 스케줄링을 통해 프로세스와 스레드가 사용 하는 CPU 실행 시간을 나눈다.
1.2 응용프로그램 아키텍처 응용프로그램의 초석은 Application 객체와 안드로이드 구성요소, 즉 액티비티Activity, 서비스 , 브로드캐스트 리시버BroadcastReceiver, 콘텐트 프로바이더ContentProvider 다.
Service
1.2.1 Application 객체 자바에서 실행 중인 응용프로그램의 표현은 android.app.Application 객체다. 이 객체는 응 용프로그램이 시작할 때 인스턴스화되고, 중지할 때 소멸된다(즉, Application 클래스의 인스 턴스는 응용프로그램의 리눅스 프로세스 수명lifetime 동안 지속된다). 프로세스가 종료되고 재시 작할 때 새로운 Application 인스턴스가 생성된다.
1.2.2 구성요소 안드로이드 응용프로그램의 기본 조각은 런타임에 의해 관리되는 구성요소(액티비티, 서비스, 브로드캐스트 리시버, 콘텐트 프로바이더)다. 이러한 구성요소의 구성 및 상호작용이 응용프로 그램의 동작을 정의한다. 이 구성요소는 서로 다른 책임과 생명주기를 갖지만, 모두 응용프로그 램을 시작할 수 있는 진입점이 된다. 구성요소가 시작되면 응용프로그램의 생명주기를 통해 다 른 구성요소들을 시작시킬 수 있다. 구성요소는 응용프로그램 내에서 또는 응용프로그램 간에 인텐트Intent 를 사용하여 다른 구성요소를 시작시킨다. 인텐트는 동작하는 수신자의 동작을 지정 (예를 들면 이메일 전송, 사진 찍기)하고, 송신자에서 수신자로 데이터를 제공할 수 있다. 인텐 트는 명시적 또는 암시적 인텐트가 될 수 있다.
1장 - 안드로이드 구성요소와 멀티프로세싱의 필요성
21
| 명시적 인텐트 | 컴파일 시 응용프로그램에서 알 수 있는 구성요소의 이름을 분명하게 나타낸다. | 암시적 인텐트 | IntentFilter 안에 여러 개의 특성으로 정의한 구성요소에 런타임 바인딩한다. 만약 해당 인텐
트가 어느 구성요소의 IntentFilter 특성과 일치한다면 그 구성요소가 시작될 수 있다. ※ 구성요소와 생명주기는 안드로이드 관련 용어로서, 이들은 하부의 자바 객체와 정확하게 일치하 지 않는다. 자바 객체는 자신의 구성요소보다 오래 살아 있을 수 있고, 그 결과 런타임은 살아 있 는 동일한 구성요소에 관련된 자바 객체를 여러 개 포함할 수 있다. 6장에서 살펴보겠지만, 이것 이 혼란의 근원이며 메모리 누수의 위험을 초래할 수 있다. 응용프로그램은 서브클래스를 이용하여 구성요소를 구현하고 응용프로그램의 모든 구성요소는
AndroidManifest.xml 파일에 등록되어야 한다.
액티비티 액티비티는 거의 언제나 기기의 전체 스크린을 차지하여 사용자에게 보이는 화면을 말하며, 이 것은 정보를 표시하거나 사용자의 입력 등을 담당한다. 그리고 화면에 표시되는 UI 구성요소 (버튼, 텍스트, 그림 등)를 포함하며, 모든 뷰 인스턴스를 담고 있는 뷰 계층구조에 대한 객체 참조를 담고 있다. 따라서 액티비티의 메모리가 차지하는 공간이 매우 커질 수 있다. 사용자가 화면 사이를 옮겨 다닐 때 액티비티 인스턴스는 스택을 형성한다. 새로운 화면으로 이 동하면 액티비티를 스택에 푸시push 하고, 이전 화면으로 돌아가면 해당 액티비티를 팝pop 한다. [그림 1-2]에서 사용자는 처음 액티비티 A를 시작하여 액티비티 B로 이동하고 A를 종료시켰 으며, 이어서 C와 D로 이동하였다. A, B, C는 모두 전체 화면을 사용하지만 D는 화면의 일부 분만 사용한다. 따라서 A는 소멸되고, B는 완전히 가려지며, C는 부분적으로 표시되고, 스택의 톱에 있는 D는 전부 표시된다. 따라서 포커스는 D에 있으며 사용자의 입력을 받는 것도 D다. 스택에서의 위치가 각 액티비티의 상태를 결정한다.
22
1장 - 안드로이드 구성요소와 멀티프로세싱의 필요성
●
포그라운드에서 활동 : D
●
일시 중지 및 부분적으로 보임 : C
●
중지되었고 눈에 보이지 않음 : B
●
비활성 및 소멸 : A
그림 1-2 액티비티 스택 B 시작 A 완료
A 시작
C 시작
D 시작
바텀(bottom)
톱(top) A
뒤로 가기
B
C
뒤로 가기
D
뒤로 가기
응용프로그램의 최상위 액티비티 상태는 결국 응용프로그램의 종료 가능성(26쪽 ‘응용프로그 램 종료’ 참조)과 스레드의 스케줄된 실행 시간에 영향을 주는 시스템 우선순위(프로세스 순위 라고도 한다)에 영향을 미친다(3장). 액티비티의 생명주기는 뒤로 가기 버튼을 눌러 사용자가 뒤로 이동하거나 액티비티가 명시적으 로 finish ( ) 함수를 호출했을 때 종료된다.
서비스 서비스는 사용자의 직접 상호작용 없이 백그라운드에서 눈에 띄지 않게 실행할 수 있다. 일반적 으로 동작이 자신의 수명보다 오래 살 수 있을 때 다른 구성요소에 실행을 떠넘기기 위해 사용된 다. 서비스는 시작started 또는 바운드bound 모드로 실행될 수 있다. | 시작 서비스 | 이 서비스는 명시적 또는 암묵적 인텐트로 Context.startService (Intent )가 호출될 때 시 작되고, Context.stopService (Intent )가 호출될 때 종료된다. 1장 - 안드로이드 구성요소와 멀티프로세싱의 필요성
23
| 바운드 서비스 | 명시적 또는 암묵적 Intent 매개변수 parameter 1와 함께 Context.bindService (Intent, ServiceConnection, int )를 호출하여 여러 구성요소를 서비스에 바인딩할 수 있다. 바인딩 후
구성요소는 ServiceConnection 인터페이스를 통해 그 서비스와 상호작용할 수 있고 Context. unbindService (ServiceConnection )을 통해 그 서비스로부터 바인딩을 해제할 수 있다. 마
지막 구성요소가 서비스로부터 바인딩 해제되면 서비스는 소멸된다.
콘텐트 프로바이더 응용프로그램 내부 혹은 응용 프로그램 사이에서 상당한 양의 데이터를 공유하려는 응용프로그 램은 콘텐트 프로바이더를 활용할 수 있다. 콘텐트 프로바이더는 어떤 데이터 소스에도 접근을 제공하지만, 응용프로그램 안에서만 사용하는 SQLite 데이터베이스를 다른 응용프로그램과 공 유할 때 주로 이용된다. 콘텐트 프로바이더의 도움으로 응용프로그램은 원격 프로세스에서 실행 되는 응용프로그램에 데이터를 공개publish 할 수 있다.
브로드캐스트 리시버 이 구성요소는 응용프로그램 내, 원격 응용프로그램, 또는 플랫폼으로부터 보내진 인텐트를 듣 는 매우 제한된 기능을 가지고 있다. 어떤 인텐트가 브로드캐스트 리시버로 보내진 것인지 가리 기 위해 수신되는 인텐트를 필터링한다. 인텐트 수신을 시작하려면 브로드캐스트 리시버를 동적 으로 등록해야 하며, 수신을 종료하려면 등록을 해제해야 한다. 만약 AndroidManifest.xml에 정적으로 등록되어 있다면 응용프로그램이 설치되어 있는 동안 인텐트를 수신할 수 있다. 따라서 어떤 인텐트가 필터에 일치할 때 브로드캐스트 리시버는 연결된 응용프로그램을 시작할 수 있다.
1.3 응용프로그램 실행 안드로이드는 동시에 여러 개의 응용프로그램을 실행할 수 있고 큰 지연 없이 응용프로그램 사 이에서 사용자 전환을 할 수 있는 멀티유저multiuser 멀티태스킹multitasking 시스템이다. 리눅스 커널 은 멀티태스킹을 처리하고, 응용프로그램 실행은 리눅스 프로세스를 기반으로 한다. 1 역자주_ 이 책에서 parameter는 매개변수로, argument는 인수로 번역했다.
24
1장 - 안드로이드 구성요소와 멀티프로세싱의 필요성
1.3.1 리눅스 프로세스 리눅스는 모든 사용자에게 기본적으로 OS에 의해 추적되는 번호인 고유한 사용자 ID를 할당한 다. 모든 사용자는 권한으로 보호되는 개인 리소스에는 접근할 수 있으나 다른 사용자의 개인 리 소스에는 접근할 수 없다(루트, 즉 슈퍼유저는 제외. 이 책에서 이를 다루지 않는다). 따라서 사 용자를 격리하기 위해 샌드박스가 만들어진다. 안드로이드에서 모든 응용프로그램 패키지는 고 유한 사용자 ID를 가진다. 예를 들어 안드로이드 응용프로그램은 리눅스에서 고유한 사용자에 해당되기 때문에 다른 응용프로그램의 리소스에 액세스할 수 없다. 응용프로그램의 각 인스턴스를 위해 안드로이드가 각 프로세스에 추가하는 것은 달빅 가상 머신 과 같은 런타임 실행 환경이다. [그림 1-3]은 리눅스 프로세스 모델, VM, 그리고 응용프로그램 사이의 관계를 보여준다. 그림 1-3 응용프로그램은 서로 다른 프로세스와 VM에서 실행된다.
리눅스 프로세스
리눅스 프로세스
달빅 VM
달빅 VM
앱A
앱B
기본적으로 응용프로그램과 프로세스는 일대일 관계지만, 필요하다면 하나의 응용프로그램을 여러 프로세스에서 실행하거나 여러 응용프로그램을 같은 프로세스에서 실행할 수 있다.
1.3.2 생명주기 응용프로그램 생명주기는 자바의 android.app.Application 클래스로 대응되는 리눅스 프 로세스 안에서 캡슐화된다. 런타임이 onCreate ( ) 함수를 호출할 때 각 응용프로그램에 대한 Application 객체가 시작된다. 원칙적으로 응용프로그램은 onTerminate ( )로 런타임 호출을
종료하지만, 응용프로그램에 의존하지 않을 수 있다. 런타임이 마지막 onTerminate ( )를 호출 할 기회가 있기 전에 하부 리눅스 프로세스가 종료되기도 한다. Application 객체는 프로세스 에 첫 번째로 생성되는 구성요소이자 마지막으로 소멸되는 구성요소다.
1장 - 안드로이드 구성요소와 멀티프로세싱의 필요성
25
응용프로그램 시작 구성요소 중 하나가 실행을 위해 초기화되면 응용프로그램이 시작된다. 어떤 구성요소든 응용프 로그램에 대한 진입점entry point 이 될 수 있다. 일단 첫 번째 구성요소가 시작되었고 프로세스가 실 행되기 전이라면 리눅스 프로세스는 다음과 같은 시동startup 순서를 거친다.
1. 리눅스 프로세스를 시작한다. 2. 런타임을 생성한다. 3. Application 인스턴스를 생성한다. 4. 응용프로그램을 위한 진입점 구성요소를 생성한다. 새로운 리눅스 프로세스와 런타임을 설정하는 작업은 짧은 시간 안에 이루어지지 않는다. 이 작 업은 성능을 저하시키고 사용자 경험에 두드러진 영향을 미칠 수 있다. 따라서 시스템은 시스템 부트에 Zygote라는 특별한 프로세스를 시작하여 안드로이드 응용프로그램의 시작 시간을 단 축하려 한다. Zygote는 미리 로드된 핵심 라이브러리의 전체 세트를 가지고 있다. 새로운 응 용프로그램 프로세스는 모든 응용프로그램에서 공유되는 핵심 라이브러리를 복사하지 않고
Zygote 프로세스에서 포크fork 한다.
응용프로그램 종료 프로세스는 응용프로그램이 시작될 때 생성되며 시스템 자원이 해제될 때 종료된다. 사용자가 나 중에 응용프로그램을 재실행할 수 있기 때문에 런타임은 시스템 전체 자원이 부족해지기 전까지 는 현재 살아 있는 응용프로그램의 모든 자원을 소멸시키지는 않는다. 따라서 모든 구성요소가 소멸해도 응용프로그램이 자동적으로 종료되지 않는다. 시스템 자원이 부족할 때 어떤 프로세스를 죽여야 할지는 런타임에 달려 있다. 이러한 결정을 하 기 위해 시스템은 애플리케이션의 가시성visibility 및 현재 실행 중인 구성요소에 따라 각각의 프로 세스에 순위를 부과한다. 낮은 순위의 프로세스는 높은 순위의 프로세스보다 먼저 강제로 종료 된다. 프로세스의 순위를 높은 순서대로 보면 다음과 같다. | 포그라운드 | 전면에 보이는 구성요소를 가진 응용프로그램, 원격 프로세스에서 전면의 액티비티에 바인딩된 서비스, 실행 중인 브로드캐스트 리시버.
26
1장 - 안드로이드 구성요소와 멀티프로세싱의 필요성
| 화면에 보이는 프로세스 | 화면에는 보이나 부분적으로 숨겨진 구성요소를 가진 응용프로그램. | 서비스 | 백그라운드에서 실행 중이고 화면에 보이는 구성요소와 연결되어 있지 않은 서비스. | 백그라운드 | 화면에 보이지 않는 액티비티. 대부분의 응용프로그램이 가지는 프로세스 레벨이다. | 빈 프로세스 | 활성화된 구성요소가 없는 프로세스. 빈 프로세스는 응용프로그램의 시작 시간을 개선하기 위 해 유지되지만 시스템이 자원을 회수할 때 가장 먼저 종료된다. ※ 실제로 순위 시스템은 자원이 부족해지면 화면에 보이는 응용프로그램이 플랫폼에 의해 종료 되지 않도록 보장한다.
상호작용하는 두 응용프로그램의 생명주기 이 예는 통상적인 방식으로 상호작용하는 두 프로세스 P1과 P2의 생명주기를 보여준다(그림
1-4). P1은 서버 응용프로그램 P2에 있는 서비스를 호출하는 클라이언트 응용프로그램이다. 클 라이언트 프로세스 P1은 브로드캐스트된 인텐트에 의해 시작된다. 시동 시 프로세스는 브로드캐 스트 리시버와 Application 인스턴스를 모두 작동시킨다. 잠시 후 액티비티가 시작되는데, 이 모든 시간 동안 P1은 가장 높은 프로세스 순위인 포그라운드가 된다. 이 액티비티는 관련된 Application 인스턴스 및 서비스를 시작한 프로세스 P2에서 실행되는 서 비스에 작업을 넘긴다. 따라서 응용프로그램은 두 가지의 서로 다른 프로세스로 작업을 나눈다.
P1 액티비티는 P2 서비스가 실행을 유지하는 동안 종료될 수 있다. 일단 모든 구성요소가 완료 되면(사용자가 P1의 액티비티에서 뒤로 가기를 눌렀고, P2의 서비스가 다른 프로세스 또는 런타 임에 의해 중지가 요청됨), 두 프로세스는 빈 프로세스가 되고, 자원이 필요할 때 적합한 종료 후 보가 된다.
1장 - 안드로이드 구성요소와 멀티프로세싱의 필요성
27
그림 1-4 클라이언트 응용프로그램은 다른 프로세스의 서비스를 시작한다.
응용프로그램 A 프로세스 P1
BR 사용자의 뒤로 가기
startActivity() 액티비티
stopService() startService() 프로세스 P2
서비스 응용프로그램 B
P1 시작
P2 시작
P2 종료
P1 종료
이 프로세스들이 실행되는 동안의 자세한 순위 목록이 [표 1-1]에 있다. 표 1-1 프로세스 순위 변화 응용프로그램 상태
P1 프로세스 순위
P2 프로세스 순위
P1은 브로드캐스트 리시버 진입점으로 시작한다.
포그라운드
없음
P1이 액티비티를 시작한다.
포그라운드
없음
P1이 P2의 서비스 진입점을 시작한다.
포그라운드
포그라운드
P1 액티비티가 소멸된다.
빈 프로세스
서비스
P2 서비스가 중지된다.
빈 프로세스
빈 프로세스
실제 리눅스 프로세스에서 정의된 응용프로그램 생명주기와 우리가 인식하는 응용프로그램 생 명주기 사이에 차이가 있음을 유의해야 한다. 응용프로그램이 종료될 때 시스템은 사용자가 인 식한 것보다 더 많은 응용프로그램 프로세스를 가질 수 있다. 시스템 자원이 허용하는 한 빈 프 로세스는 재시작할 때 시작 소요 시간을 단축하기 위해 남는다.
28
1장 - 안드로이드 구성요소와 멀티프로세싱의 필요성
1.4 성능을 위해 구조화된 응용프로그램 안드로이드 기기는 동시에 여러 작업을 실행할 수 있는 멀티프로세서 시스템이다. 하지만 작업 을 나누고 응용프로그램의 성능을 최적화하기 위해 작업을 동시에 실행하도록 보장하는 것은 각 애플리케이션에 달려 있다. 응용프로그램이 분할된 동작을 하지 않고 길게 하나의 작업으로 실 행하는 것을 선호한다면 하나의 CPU만을 이용할 수 있고 이는 차선의 성능으로 이어질 것이다. 분할되지 않은 작업은 동기적으로 실행해야 하지만, 분할된 작업은 비동기적으로 실행할 수 있 다. 비동기 작업은 시스템의 여러 CPU와 실행을 공유함으로써 처리량을 증가시킬 수 있다. 여러 개의 독립적인 태스크가 가능한 응용프로그램은 비동기 실행을 사용하도록 구성해야 한다. 한 가지 방법은 응용프로그램의 실행을 여러 프로세스에 분리시키는 것이다. 여러 개의 독립된 작업이 동시에 실행될 수 있기 때문이다. 그러나 모든 프로세스는 자신의 기본적 자원을 위해 메 모리를 할당하므로 여러 프로세스에서 응용프로그램을 실행하는 것은 하나의 프로세스에서 응 용프로그램을 실행하는 것보다 많은 메모리를 사용한다. 또한 프로세스 간의 시작과 통신이 느 리며, 비동기 실행을 하는 효과적인 방법은 아니다. 물론 여러 프로세스가 유효한 디자인일 수도 있지만, 이 결정은 성능과 독립적이어야 한다. 높은 처리량과 더 나은 성능을 만들기 위해 응용 프로그램은 각각의 프로세스 내에서 여러 스레드를 사용해야 한다.
1.4.1 스레드를 통해 반응성 있는 응용프로그램 만들기 응용프로그램은 여러 개의 CPU에서 높은 처리량을 가진 비동기 실행이 가능하지만, 반응성 있 는 응용프로그램을 보증하지는 않는다. 반응성은 상호작용하는 동안 사용자가 응용프로그램을 인식하는 방식을 말한다. 즉, UI가 버튼 클릭에 신속하게 반응하거나 부드러운 애니메이션 등 에 신속하게 반응한다는 뜻이다. 기본적으로 사용자 경험의 관점에서 성능은 응용프로그램이 얼 마나 빠르게 UI 구성요소를 업데이트하는지로 결정된다. UI 구성요소를 업데이트하는 역할은 시스템이 UI 구성요소의 업데이트를 유일하게 허용하는 UI 스레드2에 있다. 응용프로그램을 반응성 있게 만들려면, 오래 걸리는 태스크를 UI 스레드에서 실행하면 안 된다. 만일 UI 스레드에서 그런 태스크를 실행하면 해당 스레드의 다른 모든 실행이 지연될 수 있다. 일반적으로 UI 스레드에서 오래 걸리는 태스크를 실행하면 생기는 첫 번째 증상은 UI가 응답하 2 저자주_ 메인 스레드라고도 알려져 있다. 그러나 이 책 전반에 걸쳐 ‘UI 스레드’라고 부르는 관례를 지킨다.
1장 - 안드로이드 구성요소와 멀티프로세싱의 필요성
29
지 않는 것이다. 화면의 업데이트나 사용자의 버튼 누름을 적절하게 받아들일 수 없기 때문이 다. 응용프로그램이 너무 오래, 일반적으로 5~10초 동안 UI 스레드를 지연할 때, 런타임은 사 용자에게 응용프로그램의 종료 옵션을 제공하는 ‘응용프로그램이 응답하지 않습니다Application Not ’(ANR ) 대화상자를 보여준다. 분명 여러분은 이것을 피하고 싶을 것이다. 사실 런타임
Responding
은 네트워크 내려받기와 같이 시간이 오래 걸리는 특정 작업을 UI 스레드 위에서 실행하는 것을 금지한다. 따라서 긴 작업은 백그라운드 스레드에서 처리되어야 한다. 오래 걸리는 태스크는 보통 다음을 포함한다. 네트워크 통신
●
파일 읽기/쓰기
●
데이터베이스 생성/삭제/수정
●
SharedPreferences 읽기/쓰기
●
이미지 프로세싱
●
텍스트 파싱
●
오래 걸리는 태스크란 무엇인가? 백그라운드에서 실행해야 하는 오래 걸리는 태스크에 대한 정해진 정의나 명확한 지표는 없다. 그 러나 버튼이 느리게 반응하거나 더듬거리는 애니메이션처럼 사용자가 UI를 느리다고 인식한다면, 태스크를 UI 스레드에서 실행하기에는 너무 길다는 신호다. 사용자는 일반적으로 버튼 클릭보다 는 애니메이션이 UI 스레드에서 경쟁하는 태스크에 좀 더 민감하다. 인간의 두뇌는 언제 화면 터 치가 실제로 일어났는지 조금 불명확하게 인식하기 때문이다. 따라서 작업에 대한 부담이 가장 큰 사용 사례인 애니메이션으로 개략 추론을 해보자. 애니메이션은 이벤트 루프에서 업데이트되는데, 이때 모든 이벤트가 한 프레임, 즉 하나의 드로잉 주기drawing cycle 안에서 애니메이션을 업데이트한다. 시간 프레임마다 실행되는 드로잉 주기가 많을 수록 애니메이션이 더 부드럽게 인식된다. 목표가 초당 60드로잉 주기, 즉 초당 60프레임(60fps) 의 수행이라면 모든 프레임은 16ms 내에 렌더링되어야 한다. 만약 UI 스레드 위에 또 다른 태스 크가 동시에 실행되면, 드로잉 주기와 새로 실행된 두 번째 태스크 모두 16ms 내에서 완료되어야 애니메이션의 끊김을 방지할 수 있다. 따라서 이런 경우 16ms 이내에 실행될 것이 요구되는 태스 크조차 긴 태스크로 간주된다.
30
1장 - 안드로이드 구성요소와 멀티프로세싱의 필요성
이 예제와 계산은 개략적인 것이다. 몇 초간 지속되는 네트워크 연결뿐만 아니라 처음에는 문제가 없어 보이던 태스크마저 응용프로그램의 반응성에 영향을 미칠 수 있다는 점을 시사하고자 했을 뿐 이다. 응용프로그램에서는 병목현상이 어디든 숨어 있을 수 있다.
안드로이드 응용프로그램에서 스레드는 구성요소 중 어느 것만큼이나 핵심적이다. 모든 안드로 이드 구성요소와 시스템 콜백은 다른 언급이 없는 한 UI 스레드에서 실행되며, 더 오래 걸리는 태스크는 백그라운드 스레드를 사용해야 한다.
1.5 마치며 안드로이드 응용프로그램은 리눅스 프로세스에 포함된 달빅 런타임 내의 리눅스 OS 위에서 실 행된다. 안드로이드는 가장 낮은 우선순위의 응용프로그램을 종료시키기 위해 실행 중인 각 응 용프로그램의 중요성을 따져 프로세스 순위 시스템을 적용한다. 성능 향상을 위해 응용프로그램 은 코드를 동시 실행하기 위해 여러 스레드에 작업을 나눠야 한다. 모든 리눅스 프로세스는 UI 업데이트를 책임지는 특정한 스레드를 포함한다. 오래 걸리는 작업은 모두 UI 스레드에서 분리 하여 다른 스레드에서 실행해야 한다.
1장 - 안드로이드 구성요소와 멀티프로세싱의 필요성
31
Part
I
기초
1부에서는 리눅스, 자바, 안드로이드에서 제공하는 비동기 처리를 위한 빌딩 블록을 다룬다. 안드로이드 개발
자는 이들 요소의 동작 방식, 다양한 기술 사용에 관련된 상호 절충trade-off , 그리고 여기서 생겨날 수 있는 위험 요소를 이해해야 한다. 이에 대한 이해는 2부에서 소개하는 기법을 사용하는 데 기본이 된다.
Part I
기초
22장 자바의 멀티스레딩 23장 안드로이드 스레드 24장 스레드 통신 25장 프로세스 간 통신 26장 메모리 관리
CHAPTER
2
자바의 멀티스레딩
모든 안드로이드 응용프로그램은 자바 언어가 가진 멀티스레드 프로그래밍 모델을 따라야 한다. 멀티스레딩은 (좋은 사용자 경험에 필요한) 성능과 반응성의 개선을 가져오지만, 다음과 같이 복잡성의 증가도 함께 가져온다. ●
자바에서 동시 프로그램 모델 처리
●
멀티스레드 환경에서 데이터 일관성 유지
●
태스크 실행 전략 설정
2.1 스레드의 기본 소프트웨어 프로그래밍이란 결국 작업을 수행하기 위해 하드웨어로 명령어를 지시하는 일이다 (화면에 그림 보여주기, 파일시스템에 데이터 저장하기 등). 명령어는 CPU가 순차적으로 처리 하는 응용프로그램 코드로 정의되는데, 이 응용프로그램 코드를 스레드의 고수준 정의라고 한 다. 응용프로그램 관점에서 스레드는 순차적으로 수행되는 자바 문장들로 이루어진 코드 경로 code path
의 실행이다. 스레드에서 순차적으로 실행되는 코드 경로, 즉 질서 정연하게 하나의 스레
드에서 실행하는 작업의 단위를 태스크task 라고 한다. 스레드는 순차적으로 하나 또는 다수의 태 스크를 실행할 수 있다.
2장 - 자바의 멀티스레딩
35
2.1.1 실행 안드로이드 응용프로그램의 스레드는 java.lang.Thread에 의해 표현된다. 스레드는 안드로이 드에서 태스크가 시작되고 완료될 때, 태스크를 종료하거나 더 이상 실행할 태스크가 없을 때 수 행하는 가장 기본인 실행 환경이다. 스레드의 생존 시간은 태스크의 길이에 의해 결정된다. 스레 드는 java.lang.Runnable 인터페이스 구현인 태스크의 실행을 지원한다. run 메서드 안에 태 스크를 정의함으로써 구현할 수 있다. private class MyTask implements Runnable { public void run() { int i = 0; // 스레드의 지역 스택에 저장된다. } }
run ( ) 메서드 안에서 직간접적으로 호출되는 모든 지역변수는 스레드의 지역 메모리 스택local memory stack
에 저장된다. 태스크의 실행은 Thread의 객체 생성과 start ( )로 시작된다.
Thread myThread = new Thread(new MyTask()); myThread.start();
운영체제 레벨에서 스레드는 명령어 포인터instruction pointer 와 스택 포인터stack pointer 두 가지를 가지 고 있다. 명령어 포인터는 다음에 실행할 명령어를 가리키고, 스택 포인터는 스레드의 지역 데이 터를 저장하는 전용private 메모리 영역(다른 스레드에서 접근할 수 없는)을 가리킨다. 일반적으 로 스레드의 지역 데이터는 해당 응용프로그램의 자바 메서드에서 정의된 변수 리터럴이다.
CPU는 한 번에 하나의 스레드 명령어를 처리할 수 있다. 그러나 일반적으로 시스템은 동시에 실 행되는 여러 응용프로그램처럼 동시에 처리를 해야 하는 멀티스레드를 보유한다. 여러 응용프 로그램이 병렬로 실행된다고 사용자가 인식하게 하려면 CPU는 응용프로그램 스레드 사이의 처 리 시간을 공유해야 한다. CPU 처리 시간의 공유는 스케줄러scheduler 에 의해 처리된다. 스케줄러 는 CPU가 얼마나 스레드를 처리해야 하는지 결정한다. 스케줄링 전략은 다양한 방식으로 구현 될 수 있지만, 주로 스레드 우선순위를 기반으로 한다. 스레드 우선순위란 높은 우선순위의 스레 드가 낮은 우선순위의 스레드보다 먼저 CPU 할당을 받고 더 많은 실행 시간을 갖는 방식이다. 자 바에서 스레드 우선순위는 1 (최저)부터 10 (최고) 사이에서 설정할 수 있다. 명시적으로 설정하 지 않으면 보통 우선순위는 5가 된다. myThread.setPriority(8);
36
1부 - 기초
그러나 스케줄링이 우선순위 기반으로만 되면 낮은 우선순위의 스레드는 의도된 작업을 수행할 충분한 처리 시간을 얻지 못하는 경우가 발생한다. 이를 기아starvation 라고 한다. 따라서 스케줄러 가 CPU 할당을 새로운 스레드로 변경할 경우 스레드의 처리 시간도 고려해야 한다. 스레드의 변 경을 문맥 교환context switch 이라고 한다. 문맥 교환은 실행 중인 스레드를 나중에 다시 시작할 수 있도록 상태를 저장하는 것으로 시작하며, 저장된 스레드는 이제 다시 실행되길 기다려야 한다. 이제 스케줄러는 처리를 위해 대기 중이던 다른 스레드를 복원한다. [그림 2-1]에서 볼 수 있듯이 하나의 프로세서에서 동시에 실행되는 두 개의 스레드는 실행 간 격execution interval 으로 분할된다. Thread T1 = new Thread(new MyTask()); T1.start(); Thread T2 = new Thread(new MyTask()); T2.start();
그림 2-1 하나의 CPU에서 실행되는 두 개의 스레드. C는 문맥 교환을 뜻한다. 실행 간격
스레드 T1
스레드 T2
시간 C
각 스케줄링 지점은 운영체제가 스레드 전환을 수행하기 위해 CPU를 사용하는 문맥 교환을 포함한다. 문맥 교환은 그림에서 C로 표시했다.
2.1.2 싱글스레드 응용프로그램 각 응용프로그램은 실행 코드 경로를 정의하는 하나 이상의 스레드를 가진다. 만약 더 많은 스 레드가 생성되지 않으면, 모든 코드는 같은 코드 경로를 따라 처리되며, 명령어는 처리되기 전 에 모든 선행 명령어가 완료될 때까지 기다려야 한다.
2장 - 자바의 멀티스레딩
37
싱글스레드 실행은 실행 순서가 결정적deterministic 인 간단한 프로그래밍 모델이지만, 명령어가 이 전 명령어에 종속되지 않더라도 이전 명령어에 의해 상당히 지연될 수 있기 때문에 대개는 적합 한 접근법이 아니다. 예를 들어 장치의 버튼을 누른 사용자는 버튼이 눌렸다는 즉각적인 피드백 을 받아야 한다. 그러나 싱글스레드 환경에서 UI 이벤트는 이전 명령어가 실행을 마칠 때까지 지 연되므로 성능과 반응성이 저하될 수 있다. 이 문제를 해결하려면 응용프로그램을 여러 개의 코 드 경로로 실행해야 한다. 즉, 스레드로 분리할 필요가 있다.
2.1.3 멀티스레드 응용프로그램 멀티스레드를 통해 동작이 동시에 실행되는 것으로 인식하도록 응용프로그램 코드를 여러 코드 경로로 분할할 수 있다. 실행하는 스레드의 수가 프로세서의 수를 초과하면 완벽한 동시성이 될 수 없다. 그러나 스케줄러는 스레드 사이를 빠르게 전환하며, 순차적으로 처리되는 실행 간격 안 으로 모든 코드 경로를 분리하여 처리한다. 멀티스레딩은 필수적이다. 그러나 이렇게 향상된 성능에는 복잡성 증가, 메모리 소비 증가, 불 확정적(비결정적)인 실행 순서가 동반되며, 이는 응용프로그램이 관리해야 할 몫이다.
자원 소비 증가 스레드는 메모리와 프로세서 사용량 측면에서 오버헤드를 동반한다. 각 스레드는 주로 메서드 실행 중에 지역변수 및 매개변수를 저장하는 데 사용되는 전용 메모리 영역을 할당한다. 전용 메 모리 영역은 스레드가 생성될 때 할당되고 스레드가 종료되면 할당 취소된다. 즉, 스레드가 활성 화되어 있거나 심지어 유휴idle 또는 차단된blocked 경우에도 시스템 자원을 차지한다. 프로세서 측면에서는 스레드를 설정 및 해체할 때 그리고 문맥 교환에서 스레드를 저장하고 복 원할 때 오버헤드가 생긴다. 더 많은 스레드가 실행될수록 더 많은 문맥 교환이 일어나고 성능이 저하될 수 있다.
복잡성 증가 싱글스레드 응용프로그램은 실행 순서를 알 수 있기 때문에 실행을 분석하는 일이 비교적 간단 하다. 멀티스레드 응용프로그램에서는 어떻게 프로그램이 실행되고 코드가 어떤 순서로 처리
38
1부 - 기초
되는지 분석하기 상당히 어렵다. 사전에 스케줄러가 스레드 실행 시간을 할당하는 방법을 알지 못하는 한, 스레드 간 실행 순서는 불확정적indeterministic 이다. 따라서 멀티스레드는 실행에 불확실 성을 가져다준다. 이러한 불확정으로 인해 코드의 오류를 디버깅하기가 매우 어려워지며, 멀티 스레드를 조정할 필요성이 새로운 오류를 낳을 위험도 생긴다.
데이터 불일치 자원 접근의 순서가 불확정적일 때 멀티스레드 프로그램에서는 새로운 문제가 발생한다. 두 개 이상의 스레드가 공유 자원을 사용할 경우 어떤 순서로 스레드가 자원에 도달하고 처리되는지 알 수 없다. 예를 들어 스레드 T1과 T2가 멤버 변수 sharedResource를 수정하려고 할 경우, 접근 순서는 불확정적이다. 즉, 변수의 값이 먼저 증가할 수도 감소할 수도 있다. public class RaceCondition { int sharedResource = 0; public void startTwoThreads() { Thread t1 = new Thread(new Runnable() { @Override public void run() { sharedResource++; } }); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { sharedResource--; } }); t2.start(); } }
sharedResource는 경쟁 조건race condition 에 노출된다. 경쟁 조건이란 실행할 때마다 코드 실행
순서가 다를 수 있기 때문에 발생할 수 있는 상황이다. 스레드 t1이 항상 스레드 t2 앞에 오는 것 을 보장할 수 없다. 이 경우 실행 순서뿐만 아니라 증가와 감소 연산이 여러 바이트 코드 명령어 (읽기, 수정, 쓰기)를 실행한다는 문제점도 있다. 실행 순서를 따르는 sharedResource의 최종
2장 - 자바의 멀티스레딩
39
결과를 떠나, 문맥 교환이 바이트 코드 명령어 사이에서 일어날 수 있다. 즉, 결과가 0, -1, 1이 될 수 있다. 두 번째 스레드가 값을 읽기 전에 첫 번째 스레드가 값은 쓰면 첫 번째 결과(0 )가 발생하는 반면, 뒤의 두 가지 결과(-1과 1 )는 두 스레드가 모두 우선 초깃값 0을 읽고 마지막 에 쓰인 값을 최종값으로 결정할 때 발생한다. 하나의 스레드가 인터럽트되지 않아야 하는 코드 부분을 실행하는 동안 문맥 교환이 발생할 수 있으므로, 항상 다른 스레드의 간섭interleaving 없이 순차적으로 실행되는 코드 명령어의 원자 영 역atomic region 을 만드는 것이 필요하다. 하나의 스레드가 원자 영역에서 실행하고 있으면 다른 스 레드는 원자 영역에서 실행하지 못하도록 차단block된다. 자바에서 원자 영역은 하나의 스레드 만 접근할 수 있기 때문에 상호 배타적mutually exclusive 이라고 불린다. 원자 영역은 여러 가지 방법 (2.2.1절 ‘암시적인 잠금과 자바 모니터’ 참조)으로 만들 수 있지만, 가장 기본적인 동기화 메 커니즘은 synchronized 키워드다. synchronized (this) { sharedResource++; }
공유 자원에 대한 모든 접근이 동기화되면 멀티스레드로 접근해도 데이터의 일관성이 유지된 다. 이 책에서 논의되는 대부분의 스레딩 메커니즘은 이러한 오류의 위험을 줄일 수 있도록 설 계되었다.
2.2 스레드 안전 하나의 스레드는 쓰고, 다른 스레드는 읽는 등 여러 스레드에 같은 객체에 대한 접근을 제공하는 것은 스레드끼리 빠르게 통신할 수 있는 좋은 방법이다. 그러나 이 방법은 정확성을 위협할 수 있다. 멀티스레드는 공유 메모리의 상태에 대한 동시 접근을 허용하므로 객체의 동일한 인스턴 스를 동시에 실행할 수 있다. 즉, 스레드들이 어떤 상태의 값을 업데이트하기 전에 값을 읽거나, 혹은 잘못된 값을 쓰게 되는 위험을 가져온다. 여러 스레드에서 객체에 접근할 때 객체가 항상 정확한 상태를 유지해야 스레드 안전thread safety 이 보장된다. 스레드 안전은 상태에 대한 접근을 제어할 수 있도록 객체의 상태를 동기화함으로써 가능해진다. 동기화는 하나의 스레드에 의해 변경되는 도중에 다른 스레드의 접근이 가능한 모 든 변수를 읽거나 쓰는 코드에 적용되어야 한다. 이러한 코드 영역을 임계 영역critical section 이라고 40
1부 - 기초
하며, 임계 영역은 원자적으로 실행되어야 한다. 즉, 한 번에 하나의 스레드만 접근을 허용하도 록 실행되어야 한다. 동기화는 현재 임계 영역에서 실행되는 스레드가 있는지 확인하는 잠금 메 커니즘locking mechanism 으로 만들어진다. 이 경우 하나의 스레드가 임계 영역에서 실행을 완료할 때 까지 임계 영역에 진입하려는 다른 모든 스레드는 차단된다.
NOTE_ 공유 자원이 여러 스레드에서 접근 가능하고 상태가 변경될 수 있을 때, 즉 자원의 수명 동안 값이 변경될 수 있을 경우, 자원으로의 모든 접근은 동일한 잠금으로 보호할 필요가 있다.
간단히 말해, 잠금은 잠긴 지역의 원자 실행을 보장한다. 안드로이드는 다음과 같은 잠금 메커 니즘을 포함한다. ●
객체 암시적 잠금object intrinsic lock ― synchronized 키워드
●
명시적 잠금explicit lock ― java.util.concurrent.locks.ReentrantLock ― java.util.concurrent.locks.ReentrantReadWriteLock
2.2.1 암시적 잠금과 자바 모니터 synchronized 키워드는 모든 자바 객체에서 사용할 수 있는 암시적 잠금으로 동작한다. 암시
적 잠금은 상호 배타적인데, 이는 임계 영역에서 스레드의 실행이 한 스레드에 독점적임을 의미 한다. 하나의 스레드가 임계 영역을 점유하는 동안 다른 스레드의 접근은 차단되고 잠금이 해제 될 때까지 실행을 할 수 없다. 암시적 잠금은 모니터monitor (그림 2-2 )처럼 작동한다. 그림 2-2 자바 모니터 차단된 스레드
1
2
실행 중인 스레드
대기 스레드
3 5
4
2장 - 자바의 멀티스레딩
41
자바 모니터는 세 가지 상태로 모델링할 수 있다. | 차단된 스레드 | 다른 스레드에 의해 해제될 모니터를 기다리는 동안 일시 중단된 스레드. | 실행 중인 스레드 | 모니터를 소유하고 현재 임계 영역에서 코드를 실행 중인 스레드. | 대기 스레드 | 임계 영역의 끝에 도달하기 전에 자발적으로 모니터의 소유권을 포기한 스레드. 이 스레드는 다 시 소유권을 얻을 때까지 스레드의 신호를 기다린다. ※ 암시적 잠금에 의해 보호된 코드 블록에 도달하여 그것을 실행할 때, 모니터 상태 간의 스레드 전환은 다음과 같다.
1. 모니터에 진입: 스레드가 암시적 잠금에 의해 보호된 영역에 접근을 시도한다. 이 스레드 는 모니터에 들어가고 만약 다른 스레드가 이미 잠금을 차지하고 있으면 스레드의 잠금 획 득이 연기된다.
2. 잠금 획득: 모니터를 소유하고 있는 다른 스레드가 없는 경우, 차단된 스레드는 소유권을 획득하고 임계 영역에서 실행할 수 있다. 하나 이상의 차단된 스레드가 있는 경우, 스케줄 러는 실행할 스레드를 선택한다. 차단된 스레드 간에 선입선출first in first out (FIFO ) 순서는 없다. 즉, 모니터에 첫 번째로 들어온 스레드가 반드시 먼저 선택되어야 하는 것은 아니다.
3. 잠금 해제 및 대기: 스레드는 계속 실행하기 전에 충족해야 할 조건을 기다려야 하므로 Object.wait ( )를 통해 스레드 자신의 실행을 일시 중단한다.
4. 신호 후 잠금 획득: 대기 스레드가 Object.notify ( ), Object.notifyAll ( )을 통해 다 른 스레드로부터 신호를 받고 스케줄러에 의해 선택되면 다시 모니터의 소유권을 가질 수 있다. 그러나 대기 스레드가 모니터를 소유할 가능성은 잠재적으로 차단된 스레드보다 앞 설 수 없다.
5. 잠금 해제 및 모니터 종료: 임계 영역의 끝에서 스레드는 모니터를 종료하고 다른 스레드 가 모니터를 소유할 수 있도록 자리를 떠난다. 42
1부 - 기초
이러한 전환은 아래 동기화 코드 블록의 번호에 대응한다. synchronized (this) { (1) // 코드 실행 (2) wait(); (3) // 코드 실행 (4) } (5)
2.2.2 공유 자원 접근의 동기화 여러 스레드에서 접근하고 변경할 수 있는 공유된 변경 가능한 상태shared mutable state 는 동시 실행 중에 일관된 데이터를 유지하기 위해 동기화 전략이 필요하다. 이 전략은 상황에 따라 올바른 잠 금 종류를 선택하고 임계 영역의 범위를 설정하는 것을 포함한다.
암시적 잠금 사용 암시적 잠금은 synchronized 키워드가 사용되는 방식에 따라 다른 방법으로 공유된 변경 가능 한 상태를 보호할 수 있다. 객체 인스턴스를 둘러싸는 암시적 잠금으로 작동하는 메서드 레벨
●
synchronized void changeState() { sharedResource++; } 객체 인스턴스를 둘러싸는 암시적 잠금으로 작동하는 블록 레벨
●
void changeState() { synchronized(this) { sharedResource++; } } 다른 객체의 암시적 잠금을 가지는 블록 레벨
●
private final Object mLock = new Object(); void changeState() { synchronized(mLock) { sharedResource++; } }
2장 - 자바의 멀티스레딩
43
클래스 인스턴스를 둘러싸는 암시적 잠금으로 작동하는 메서드 레벨
●
synchronized static void changeState() { staticSharedResource++; } 클래스 인스턴스를 둘러싸는 암시적 잠금으로 작동하는 블록 레벨
●
static void changeState() { synchronized(MyClass.class) { staticSharedResource++; } }
블록 레벨의 동기화에서 this 객체에 대한 참조 방법(두 번째)은 메서드 레벨 동기화(첫 번째) 와 동일한 암시적 잠금을 사용한다. 그러나 두 번째 구문을 사용하면 임계 영역에 포함된 코드 의 정확한 블록을 제어할 수 있고, 따라서 실제로 보호될 상태와 관련된 코드만 줄여서 다룰 수 있다. 필요 이상으로 원자 영역을 크게 만드는 것은 나쁜 습관인데, 필요하지 않은 곳까지 다른 스레드를 차단할 수 있고, 이는 응용프로그램의 느린 실행을 가져올 수 있기 때문이다. 다른 객체의 암시적 잠금을 동기화하는 것은 클래스 내에서 여러 개의 잠금을 사용할 수 있게 한 다. 응용 프로그램은 자신의 잠금과 함께 각각 독립된 상태를 보호하기 위해 노력해야 한다. 따라 서 클래스가 하나 이상의 독립적인 상태를 가진 경우, 여러 잠금을 사용하면 성능이 개선된다.
CAUTION_ synchronized 키워드는 다양한 암시적 잠금을 제공할 수 있다. 정적 메서드에 대한 동기화 는 인스턴스 객체가 아닌 클래스 객체의 암시적 잠금으로 동작한다는 것을 기억하라.
명시적 잠금 메커니즘 사용 좀 더 향상된 고급 잠금 전략이 필요한 경우 synchronized 키워드 대신 ReentrantLock 또는 ReentrantReadWriteLock 클래스를 사용한다. 임계 영역은 코드의 특정 부분에 대한 명시적
잠금 및 해제에 의해 보호받는다. int sharedResource; private ReentrantLock mLock = new ReentrantLock(); public void changeState() { mLock.lock();
44
1부 - 기초
try { sharedResource++; } finally { mLock.unlock(); } }
synchronized 키워드와 ReentrantLock은 같은 의미를 가진다. 두 기법은 모두 한 스레드가
이미 임계 영역에 들어온 경우 임계 영역을 실행하려는 모든 스레드를 차단한다. 이는 임계 영역 에 대한 모든 동시 접근이 문제가 되지만, 멀티스레드가 동시에 공유 변수를 읽는 것이 유해하지 않다고 가정하는 방어적인 전략이다. 따라서 synchronized와 ReentrantLock은 과잉보호로 이 어질 수도 있다. ReentrantReadWriteLock은 읽으려는 스레드는 동시에 실행되게 내버려두지만, 읽기 대 쓰기,
쓰기 대 쓰기의 경우는 차단한다. int sharedResource; // 공유 자원 private ReentrantReadWriteLock mLock = new ReentrantReadWriteLock(); // 공유 자원의 값을 변경하는 메서드(임계 영역에 하나의 스레드만 접근 가능) public void changeState() { mLock.writeLock().lock(); try { sharedResource++; } finally { mLock.writeLock().unlock(); } } // 공유 자원의 값을 읽는 메서드(여러 스레드에서 동시 접근 가능) public int readState() { mLock.readLock().lock(); try { return sharedResource; } finally { mLock.readLock().unlock(); } }
ReentrantReadWriteLock은 스레드가 허용 또는 차단될 수 있는지 확인하므로 synchronized
와 ReentrantLock보다 상대적으로 복잡하고, 성능 저하를 가져올 수 있다. 따라서 여러 스레드
2장 - 자바의 멀티스레딩
45
가 동시에 공유 자원을 읽게 두는 것과 평가의 복잡성에서 오는 성능 손실 사이에 상호 절충이 있 는 셈이다. ReentrantReadWriteLock의 일반적인 좋은 사용 사례는 여러 읽기 스레드와 약간의 쓰기 스레드가 있는 경우다.
2.2.3 예제: 소비자와 생산자 스레드끼리 공동으로 작업하는 흔한 사용 사례로, 한 스레드는 데이터를 생산하고 한 스레드는 데이터를 소비하는 소비자-생산자 패턴consumer-producer pattern 이 있다. 스레드는 자신들 사이에 공 유되는 리스트를 통해 공동 작업을 수행할 수 있다. 리스트가 가득 차지 않은 경우 생산자 스레 드는 리스트에 항목을 추가하고, 리스트가 비어 있지 않은 경우 소비자 스레드는 항목을 제거 한다. 리스트가 가득 찬 경우 생산하는 스레드가 차단되며, 리스트가 비어 있는 경우 소비하는 스레드가 차단된다. 다음 ConsumerProducer 클래스는 공유 자원 LinkedList와 두 가지 메서드(항목을 추가하는 produce와 항목을 제거하는 consume )를 포함한다. public class ConsumerProducer { private LinkedList<Integer> list = new LinkedList<Integer>(); private final int LIMIT = 10; private Object lock = new Object(); public void produce() throws InterruptedException { int value = 0; while (true) { synchronized (lock) { while(list.size() == LIMIT) { lock.wait(); } list.add(value++); lock.notify(); } } } public void consume() throws InterruptedException {
46
1부 - 기초
while (true) { synchronized (lock) { while(list.size() == 0) { lock.wait(); } int value = list.removeFirst(); lock.notify(); } } } }
produce ( )와 consume ( )은 공유 리스트를 보호하기 위해서 동일한 암시적 잠금을 사용한다.
리스트에 접근하려는 스레드는 다른 스레드가 모니터를 소유하는 동안 차단된다. 그러나 리스트 가 가득 차면 생산자 스레드가 wait ( )로 중단되고, 리스트가 비면 소비자 스레드가 wait ( )로 중단된다. 항목이 리스트에 추가되거나 제거될 때는, 대기 스레드를 다시 실행하기 위해 notify ( )를 호출 하여 모니터에 신호를 보낸다. 소비자 스레드는 생산자 스레드에 신호를 보내고, 생산자 스레드 는 소비자 스레드에 신호를 보낸다. 다음 코드는 생산과 소비 동작을 실행하는 두 개의 스레드를 보여준다. final ConsumerProducer cp = new ConsumerProducer(); new Thread(new Runnable() { @Override public void run() { try { cp.produce(); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { cp.consume(); } catch (InterruptedException e) {
2장 - 자바의 멀티스레딩
47
e.printStackTrace(); } } }).start();
2.3 태스크 실행 전략 멀티스레드가 반응성 있는 응용프로그램을 만드는 데 적절히 사용되게 하려면 응용프로그램을 설계할 때 스레드 생성과 태스크 실행을 염두에 두어야 한다. 이상적이지 않은 극단적인 설계 두 가지를 살펴보자. | 모든 태스크에 하나의 스레드 사용 | 모든 태스크가 동일한 스레드에서 실행된다. 그 결과 종종 가용한 프로세서를 사용하지 않아 반 응성 없는 응용 프로그램이 될 수 있다. | 각각의 태스크당 하나의 스레드 사용 | 각각의 태스크에 대해 시작되고 종료되는 새로운 스레드에서 항상 태스크를 실행한다. 태스크 가 자주 생성되며 수명이 짧은 경우에는 스레드 생성과 해체의 오버해드는 성능을 저하시킬 수 있다. ※ 이러한 극단은 피해야 하지만, 이 둘은 순차 실행과 동시 실행의 한 극단적인 형태를 대표한다. | 순차 실행 | 다음 태스크가 처리되기 전에 하나의 태스크를 완료하는 순서로 실행된다. 따라서 태스크의 실 행 간격이 중첩되지 않는다. 이 설계의 장점은 다음과 같다. 본질적으로 스레드 안전thread safe 하다.
●
멀티스레드보다 적은 메모리를 소비하는 하나의 스레드로 실행할 수 있다.
●
이 설계의 단점은 다음과 같다.
48
1부 - 기초
처리량throughput 이 적다.
●
각 태스크의 실행 시작이 이전에 실행된 태스크에 따라 달라진다. 시작이 지연되거나 전혀 실행되지 않을 수도
●
있다.
| 동시 실행 | 태스크는 병렬 및 인터리브interleave 1로 실행된다. 이 실행은 더 나은 CPU 사용률이란 장점을 가 지지만, 단점으로는 본질적으로 스레드 안전하지 않으므로 동기화가 요구될 수 있다. ※ 효과적인 멀티스레드 설계는 순차 실행과 동시 실행을 통해 실행 환경을 활용한다. 선택은 태스 크의 종류에 따라 달라진다. 고립되고 독립적인 태스크는 처리량을 증가시키기 위해 동시 실행 할 수도 있지만, 순서가 필요하거나 동기화 없이 공통 자원을 공유하는 태스크는 순차 실행해야 한다.
2.3.1 동시 실행 설계 동시 실행은 다양한 방법으로 구현될 수 있으므로 설계 시 실행하는 스레드의 개수와 그들의 관 계를 관리하는 방법을 고려해야 한다. 기본 원칙은 다음과 같다. 자원의 생성과 해체의 빈도를 감소시키기 위해 항상 새로운 스레드를 만드는 것보다 재사용을 권장한다.
●
필요 이상으로 스레드를 사용하지 않는다. 사용하는 스레드가 많을수록 더 많은 메모리와 프로세서 시간이 소
●
비된다.
2.4 마치며 안드로이드 응용프로그램은 싱글 및 멀티프로세서 플랫폼에서 성능을 향상하기 위해 멀티스레 드로 만들어야 한다. 스레드는 하나의 프로세서에서 실행을 공유하거나 멀티프로세서를 사용할 수 있을 때 진정한 동시성을 활용할 수 있다. 향상된 성능은 증가한 복잡성뿐만 아니라, 스레드 간의 공유 자원을 보호하고 데이터 일관성을 유지해야 하는 책임을 비용으로 가져온다. 1 역자주_ 인터리브는 처리할 데이터의 의존성을 없애 동시에 실행하는 방법이다.
2장 - 자바의 멀티스레딩
49
CHAPTER
3
안드로이드 스레드
모든 안드로이드 응용프로그램은 내부 실행을 관리하는 리눅스 프로세스 및 달빅 VM에 번들된 다수의 스레드와 함께 시작된다. 그러나 응용프로그램은 UI 스레드 및 바인더 스레드 같은 시 스템 스레드를 외부에서 제어할 수 있도록 노출하고 자신만의 백그라운드 스레드를 생성한다. 이 장에서는 다음 항목들을 살펴봄으로써 안드로이드 플랫폼에서의 스레드를 간단히 살펴볼 것이다. ●
UI, 바인더, 백그라운드 스레드의 유사점과 차이점
●
리눅스 스레드 결합coupling
●
스레드 스케줄링이 응용프로그램 프로세스의 순위에 영향을 받는 방식
●
리눅스 스레드 실행
3.1 안드로이드 응용프로그램 스레드 모든 응용프로그램 스레드는 자바의 Thread를 표현한 리눅스의 네이티브 pthreads를 기반으로 하지만, 안드로이드 플랫폼은 pthreads와 다르게 하려고 특별한 속성을 할당했다. 응용프로그 램 관점에서 스레드는 UI, 바인더, 백그라운드 스레드 등 3가지 유형이 있다.
3장 - 안드로이드 스레드
51