关于android:Kotlin-线程同步的方法

48次阅读

共计 6073 个字符,预计需要花费 16 分钟才能阅读完成。

面试的时候常常会被问及多线程同步的问题,例如:

“现有 Task1、Task2 等多个并行任务,如何期待全副工作执行实现后,开始执行 Task3 ?”

Kotlin 中有多种实现形式可供选择,本文将所有这些形式做了整顿:

  1. Thread.join
  2. Synchronized
  3. ReentrantLock
  4. BlockingQueue
  5. CountDownLatch
  6. CyclicBarrier
  7. CAS
  8. Future
  9. CompletableFuture
  10. Rxjava
  11. Coroutine
  12. Flow

咱们先定义三个 Task,模仿上述场景,Task3 基于 Task1、Task2 返回的后果拼接字符串,每个 Task 通过 sleep 模仿耗时:

val task1: () -> String = {sleep(2000)
    "Hello".also {println("task1 finished: $it") }
}

val task2: () -> String = {sleep(2000)
    "World".also {println("task2 finished: $it") }
}

val task3: (String, String) -> String = { p1, p2 ->
    sleep(2000)
    "$p1 $p2".also {println("task3 finished: $it") }
}

1. Thread.join()

Kotlin 兼容 Java,Java 的所有线程工具默认都能够应用。其中最简略的线程同步形式就是应用 Thread 的 join()

@Testfun test_join() {    lateinit var s1: String    lateinit var s2: String    val t1 = Thread { s1 = task1() }    val t2 = Thread {s2 = task2() }    t1.start()    t2.start()    t1.join()    t2.join()        task3(s1, s2)}

2. Synchronized

应用 synchronized 锁进行同步

 @Test
    fun test_synchrnoized() {
        lateinit var s1: String
        lateinit var s2: String
        Thread {synchronized(Unit) {s1 = task1()
            }
        }.start()
        s2 = task2()

        synchronized(Unit) {task3(s1, s2)
        }

    }

然而如果超过三个工作,应用 synchrnoized 这种写法就比拟顺当了,为了同步多个并行任务的后果须要申明 n 个锁,并嵌套 n 个 synchronized

3. ReentrantLock


ReentrantLock 是 JUC 提供的线程锁,能够替换 synchronized 的应用

 @Test
    fun test_ReentrantLock() {

        lateinit var s1: String
        lateinit var s2: String

        val lock = ReentrantLock()
        Thread {lock.lock()
            s1 = task1()
            lock.unlock()}.start()
        s2 = task2()

        lock.lock()
        task3(s1, s2)
        lock.unlock()}

ReentrantLock 的益处是,当有多个并行任务时是不会呈现嵌套 synchrnoized 的问题,但依然须要创立多个 lock 治理不同的工作,

4. BlockingQueue

阻塞队列外部也是通过 Lock 实现的,所以也能够达到同步锁的成果

 @Test
    fun test_blockingQueue() {

        lateinit var s1: String
        lateinit var s2: String

        val queue = SynchronousQueue<Unit>()

        Thread {s1 = task1()
            queue.put(Unit)
        }.start()

        s2 = task2()

        queue.take()
        task3(s1, s2)
    }

当然,阻塞队列更多是应用在生产 / 生产场景中的同步。

5. CountDownLatch

JUC 中的锁大都基于 AQS 实现的,能够分为独享锁和共享锁。ReentrantLock 就是一种独享锁。相比之下,共享锁更适宜本场景。例如 CountDownLatch,它能够让一个线程始终处于阻塞状态,直到其余线程的执行全副实现:

 @Test
    fun test_countdownlatch() {
        lateinit var s1: String
        lateinit var s2: String
        val cd = CountDownLatch(2)
        Thread() {s1 = task1()
            cd.countDown()}.start()

        Thread() {s2 = task2()
            cd.countDown()}.start()

        cd.await()
        task3(s1, s2)
    }

共享锁的益处是不用为了每个工作都创立独自的锁,即便再多并行任务写起来也很轻松

6. CyclicBarrier

CyclicBarrier 是 JUC 提供的另一种共享锁机制,它能够让一组线程达到一个同步点后再一起持续运行,其中任意一个线程未达到同步点,其余已达到的线程均会被阻塞。

与 CountDownLatch 的区别在于 CountDownLatch 是一次性的,而 CyclicBarrier 能够被重置后重复使用,这也正是 Cyclic 的命名由来,能够循环应用

 @Test
    fun test_CyclicBarrier() {

        lateinit var s1: String
        lateinit var s2: String
        val cb = CyclicBarrier(3)

        Thread {s1 = task1()
            cb.await()}.start()

        Thread() {s2 = task1()
            cb.await()}.start()

        cb.await()        task3(s1, s2)

    }

7. CAS

AQS 外部通过自旋锁实现同步,自旋锁的实质是利用 CompareAndSwap 防止线程阻塞的开销。因而,咱们能够应用基于 CAS 的原子类计数,达到实现无锁操作的目标。

  @Test
    fun test_cas() {

        lateinit var s1: String
        lateinit var s2: String

        val cas = AtomicInteger(2)

        Thread {s1 = task1()
            cas.getAndDecrement()}.start()

        Thread {s2 = task2()
            cas.getAndDecrement()}.start()

        while (cas.get() != 0) {}

        task3(s1, s2)

    }

while 循环空转看起来有些浪费资源,然而自旋锁的实质就是这样,所以 CAS 仅仅实用于一些 cpu 密集型的短工作同步。

volatile

看到 CAS 的无锁实现,兴许很多人会想到 volatile,是否也能实现无锁的线程平安?

  @Test
    fun test_Volatile() {
        lateinit var s1: String
        lateinit var s2: String

        Thread {s1 = task1()
            cnt--
        }.start()

        Thread {s2 = task2()
            cnt--
        }.start()

        while (cnt != 0) { }

        task3(s1, s2)

    }

留神,这种写法是谬误的volatile 能保障可见性,然而不能保障原子性,cnt-- 并非线程平安,须要加锁操作

8. Future


下面无论有锁操作还是无锁操作,都须要定义两个变量 s1s2 记录后果十分不不便。Java 1.5 开始,提供了 Callable 和 Future,能够在工作执行完结时返回后果。

@Testfun test_future() {val future1 = FutureTask(Callable(task1))
    val future2 = FutureTask(Callable(task2))

    Executors.newCachedThreadPool().execute(future1)
    Executors.newCachedThreadPool().execute(future2)

    task3(future1.get(), future2.get())

}

通过 future.get(),能够同步期待后果返回,写起来十分不便

9. CompletableFuture

future.get() 尽管不便,然而会阻塞线程。Java 8 中引入了 CompletableFuture,他实现了 Future 接口的同时实现了 CompletionStage 接口。CompletableFuture 能够针对多个 CompletionStage 进行逻辑组合、实现简单的异步编程。这些逻辑组合的办法以回调的模式防止了线程阻塞:

@Testfun test_CompletableFuture() {CompletableFuture.supplyAsync(task1)
        .thenCombine(CompletableFuture.supplyAsync(task2)) { p1, p2 ->
             task3(p1, p2)
        }.join()}

10. RxJava

RxJava 提供的各种操作符以及线程切换能力同样能够帮忙咱们实现需求:zip 操作符能够组合两个 Observable 的后果;subscribeOn 用来启动异步工作

@Testfun test_Rxjava() {

    Observable.zip(Observable.fromCallable(Callable(task1))
            .subscribeOn(Schedulers.newThread()),
        Observable.fromCallable(Callable(task2))
            .subscribeOn(Schedulers.newThread()),
        BiFunction(task3)
    ).test().awaitTerminalEvent()

}

11. Coroutine

后面讲了那么多,其实都是 Java 的工具。Coroutine 终于算得上是 Kotlin 特有的工具了:

@Testfun test_coroutine() {

    runBlocking {val c1 = async(Dispatchers.IO) {task1()
        }

        val c2 = async(Dispatchers.IO) {task2()
        }

        task3(c1.await(), c2.await())
    }
}

写起来特地难受,能够说是集后面各类工具的长处于一身。

12. Flow

Flow 就是 Coroutine 版的 RxJava,具备很多 RxJava 的操作符,例如 zip:

@Test
fun test_flow() {val flow1 = flow<String> { emit(task1()) }
    val flow2 = flow<String> {emit(task2()) }

            runBlocking {flow1.zip(flow2) { t1, t2 ->
             task3(t1, t2)
        }.flowOn(Dispatchers.IO)
        .collect()}

}

flowOn 使得 Task 在异步计算并发射后果。

总结

下面这么多形式,就像茴香豆的“茴”字的四种写法,没必要都把握。作为论断,在 Kotlin 上最好用的线程同步计划首推协程!
原文链接:面试必备:Kotlin 线程同步的 N 种办法 – 掘金 (juejin.cn)

文末

您的点赞珍藏就是对我最大的激励!
欢送关注我,分享 Android 干货,交换 Android 技术。
对文章有何见解,或者有何技术问题,欢送在评论区一起留言探讨!

正文完
 0