개발/안드로이드

Thread, Runnable, Callable, ThreadPool

kwony 2021. 4. 23. 17:29

1. Thread

 

Thread 클래스는 Java 언어에서 비동기 작업시 대표적으로 사용하는 클래스다. 코틀린과 람다를 이용하면 아래와 같이 간단하게 비동기 작업 코드를 짤 수 있어서 짧은 디코딩 작업이나, 연산처리를 할 때 주로 사용된다. 

 

fun testThread() {
    val thread1 = Thread {
        Thread.sleep(1000)
        Log.d(this.toString(), "this is test thread1")
    }

    val thread2 = Thread {
        Log.d(this.toString(), "this is test thread2")
    }

    thread1.start()
    thread2.start()
}

 

그런데 Thread를 생성하는 작업은 안드로이드 밑단 운영체제에서 pthread를 생성하게 되는 작업이므로 추가적인 메모리를 할당받게 된다. 그래서 Thread를 많이 생성할수록  더 많은 메모리 공간을 차지하게되기 때문에 무한정 만들 수 없다. 

 

2. Runnable 

 

앞서 언급한 Thread를 간소화한 형태다. Thread는 클래스인 반면 Runnable은 인터페이스라서 다른 클래스를 상속하고 있는 클래스에 Runnable을 추가할 수 있고 상속 형태 없이 익명 클래스 형태로 바로 써도 된다. 실행은 run() 클래스를 호출해서 가능하다. 

 

fun testRunnable() {
    val runnable = Runnable { Log.d(this.toString(), "test runnable") }

    runnable.run()
}

 

3. Callable 

 

Runnable과 비슷하지만 Callable은 비동기 작업에서 리턴값도 줄 수 있고 결과 값을 연산하지 못하면 Exception을 발생시키기도 한다. 리턴값을 제공해야하는 비동기작업이면 Runnable 말고 Callable을 쓰면 된다. 아래 코드는 Callable에서 정수 값들을 리턴하도록 하고 각각의 합을 로그로 찍은 것이다

 

fun testCallable() {
    val callableOne = Callable { 1 }
    val callableTwo = Callable { 2 }
    val callableLog = Callable { Log.d(this.toString(), "this is callable log") }

    try {
        val merged = callableOne.call() + callableTwo.call()
        callableLog.call()

        Log.d(this.toString(), "this is callable merged: $merged")
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

 

4. ThreadPool

 

앞서 설명한 Thread, Runnable, Callable 모두 쓰레드 계열이라서 개별로 메모리를 쓰게돼, 무한정 만들면 메모리 낭비가 발생한다. 그래서 자바에서는 ThreadPool이란 것으로 쓰레드를 관리할 수 있도록 했다.  ThreadPool 관리 객체는 Executors 클래스를 통해서 생성하는데 이 클래스 검색해보면 내부의 함수를 통해 다양한 방식으로 Thread를 관리할 수 있는 것을 확인 할 수 있다. 지면상 모두 설명할 수 없고, 아래 코드에서 쓰는 newFixedThreadPool 함수만 설명하면 쓰레드를 최대 4개만 만들 수 있게 하고 나머지 작업은 재사용하겠다는 듯이다. 새로운 작업을 추가해도 쓰레드는 4개 이상 생기지 않으며 나머지는 재사용되기 때문에 메모리 낭비에 대한 부담 없이 새로운 작업을 추가할 수 있다.

 

작업은 Callable, Runnable의 형태로 추가 가능하며 submit 함수로 추가한다. 이때 리턴 값은 Future 클래스로 받게되고, get() 함수를 통해 결과 값을 얻을 수 있다.

 

fun testExecutorService() {
    val cachedPool = Executors.newFixedThreadPool(4)
    val futureOne = cachedPool.submit(Callable { 1 })
    val futureTwo = cachedPool.submit(Callable { 2 })
    val printLog = cachedPool.submit(Runnable {
        Log.d(this.toString(), "something...") })

    try {
        val merged = futureOne.get() + futureTwo.get()
        printLog.get()
        Log.d(this.toString(), "merged: $merged")

    } catch (e: Exception) {
        e.printStackTrace()
    }
}