是的,这个话题还没有说完。如果能用三言两语概括,它就不是并发编程了。
接下来是一些难以概括的知识点,但仍然很有用。
CompletableFuture
Java 8 新加入的特性,比起Future,它支持链式调用和回调函数,让复杂的异步操作变得容易。然而,回调函数的底层编排比较复杂(它属于Java标准库最难看懂,也是最”complicated”的一些),但调用者一般无需考虑它们的实现。
它的声明方式比较独立。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "hello");
future.get()可以等待结果。这和普通的Future一样会阻塞。
然而,它的特点在于灵活的回调函数。(可能会想到JavaScript的Promise,但CompletableFuture背后并没有事件循环)可以像这样优雅地链式调用:
CompletableFuture.supplyAsync(() -> 1)
.thenApply(i -> i + 1)
.thenApply(i -> 1 * 2);
这里思路就打开了。
常用方法
CompletableFuture.runAsync()创建的任务没有返回值,一般不做链式调用。CompletableFuture.supplyAsync()有返回值。
thenApply()传入一个函数(Function),在运行到此处时执行函数。
thenAccept()传入一个Customer,它通常放在调用链的最后,因为执行后会返回CompletableFuture<Void>。
thenRun()只是运行函数,此函数不会得到参数,但会在特定时机运行。
exceptionally()接受一个函数,它有一个参数,是异常的Exception类。它会捕获它之前整个调用链中出现的异常,除非它们在自己的函数里已经被处理。
CompletableFuture.allOf(CompletableFuture a, b, c...)可以组合几个任务,它们是并发的。只有这些任务全部完成,它才会执行下一步。
CompletableFuture.anyOf(a, b, c)与前者类似,但任意任务完成就会执行下一步。
有趣的事实(也许你已经知道了):CompletableFuture是一个 Monad 。
ReadWriteLock
一个著名的规律是,有多个线程同时读取某个资源,或者单个线程写入某个资源时,是线程安全的;但一读一写或者有多个写入者时,就可能出现问题。Java的ReadWriteLock就是为了解决这种问题的。
读取者和写入者加锁的方式不一样,分别是lock.readLock().lock()和lock.writeLock().lock()。它们可以分别unlock。
与一般的锁不同,它允许多个读取者同时存在,在读多写少的情景下能大幅提升效率。
Condition
类似于Object的wait/notify方法,Condition用于表示一个状态,即某个资源是否已存在。它们很适合生产者/消费者模型。
Condition一般派生自Lock,它们总是和某个锁绑定。声明方式类似Condition c = lock.newCondition()。
Condition拥有await(),signal(),signalAll()等方法,和经典的P/V操作如出一辙。
发表回复