안드로이드는 UI 스레드에서 데이터베이스에 접근하는 것을 제한하고 있다. 데이터베이스에 접근하기 위해선 다른 스레드를 생성해서 처리해줘야 한다. 이러한 비동기 작업을 하기 위해선 여러 방법이 있지만, 이번 포스팅에서는 그중 하나인 Executors를 활용한 방법을 다룰 것이다.
Java에서는 java.util.concurrent.Executors와 java.util.concurrent.ExecutorService를 제공하며 이를 이용하면 간단히 스레드 풀을 생성하여 병렬 처리를 할 수 있다. Executors 클래스에서의 여러 static 메서드를 사용해 ExecutorService 인터페이스의 구현 객체를 만들 수 있는데, 이러한 객체가 스레드 풀이다.
스레드 풀은 작업 처리에 사용되는 스레드를 제한된 개수만큼 정해 놓고 작업 큐(Queue)에 들어오는 작업들을 하나씩 스레드가 맡아 처리한다.
ExecutorService와 Thread Pool의 구조
Executor Service는 스레드 풀과 Queue로 구성되어 있다. 제출된 task들은 Queue에 들어가게 되고 순차적으로 스레드에 할당된다. 스레드가 만약 남아있지 않다면 Queue 안에서 대기하게 된다. 스레드를 생성하는 것은 비용이 큰 작업이기 때문에 이를 최소화하기 위해 미리 스레드 풀 안에 스레드를 생성해 놓고 관리한다.
ExecutorService 객체 생성
Executors는 아래의 메서드들을 제공하여 스레드 풀의 개수 및 종류를 정할 수 있다.
- newFixedThreadPool(int) : 인자 개수만큼의 고정된 스레드 풀을 생성한다.
- newCachedThreadPool() : 필요할 때, 필요한 만큼의 스레드 풀을 생성한다.
- newSingleThreadExecutor() : 스레드가 1개인 ExecutorService를 리턴한다. 싱글 스레드에서 동작해야 하는 작업을 처리할 때 사용한다.
ExecutorService 예제
필자는 안드로이드에 적용할 목적으로 공부하고 있기 때문에 코드가 kotlin으로 되어있다.
fun main() {
val executor = Executors.newFixedThreadPool(2)
(0..9).forEach { _ ->
executor.execute {
try {
TimeUnit.MILLISECONDS.sleep(1000)
val threadName = Thread.currentThread().name
println("hello $threadName")
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
}
executor.shutdown()
if(executor.awaitTermination(10, TimeUnit.SECONDS)) {
println("작업 종료")
} else {
println("작업 종료 X")
executor.shutdownNow()
}
}
순차적으로 코드를 분석해보자.
첫 줄에 Executors.newFixedThreadPool(2)로 ExecutorService 객체를 생성해준다. 이때 파라미터 값으로 2를 설정해주었기 때문에 스레드 풀 안에 있는 스레드의 개수는 2개가 된다. 10번 반복해서 스레드 풀에 일을 주면 스레드 풀 안의 스레드 하나가 선택되어 일을 처리하게 된다.
execute() 메서드를 사용하면 void를 리턴하게 된다. 즉, 일처리를 시키기만 하고 결과를 받지 않게 된다. 만약 결괏값까지 받고 싶다면 submit() 메서드를 사용하면 된다.
shutdown() 메서드는 작업이 모두 완료되면 스레드 풀을 종료시키는 메서드다. awaitTermination() 메서드는 이미 수행 중인 Task가 지정된 시간 동안 끝나기를 기다린다. 지정된 시간 내에 끝나지 않으면 false를 리턴하며, shutdownNow() 메서드로 실행 중인 task를 모두 강제 종료할 수 있다.
Future 객체
위의 예제에서 submit() 메서드를 사용하면 결괏값을 받을 수 있다고 언급했다. 이때 결괏값은 Future 객체로 반환되게 되며, Future를 이용하면 예약된 작업에 대한 결과를 알 수 있다.
fun submit() {
val executor = Executors.newFixedThreadPool(2)
val integers = listOf(1, 2, 3, 4, 5)
val future = executor.submit<Int> {
TimeUnit.MILLISECONDS.sleep(500)
integers.sum()
}
executor.shutdown()
try {
val result = future.get()
print("result: $result")
} catch (e: InterruptedException) {
e.printStackTrace()
} catch (e: ExecutionException) {
e.printStackTrace()
}
}
위의 예제는 1부터 5까지 더하는 일을 스레드한테 시켜서 해당 반환 값을 리턴 받는 예제이다. 리턴된 값은 Future 객체의 get() 메서드를 사용해서 얻을 수 있다.
출처 : https://www.callicoder.com/java-executor-service-and-thread-pool-tutorial/
'Android' 카테고리의 다른 글
[Android] 룸(Room) 지속성 라이브러리 (0) | 2020.05.30 |
---|---|
[Android] Jetpack Databinding이란? (0) | 2020.05.28 |
[Android] MediatorLiveData가 동작하지 않을 때 (1) | 2020.05.13 |
[Android] LiveData 변형하기 (Transformations) (0) | 2020.04.22 |
[Android] Jetpack WorkManager란? (0) | 2020.04.20 |