Scala併發程式設計實戰 - 2:Lock 鎖
synchronized作為內建鎖,使用簡單,不易出錯,然鵝確有相當的侷限性,例如,無法從等待獲取鎖的阻塞中中斷,無法設定獲取鎖的超時。所以JUC提供了另一種更靈活的加鎖方式,即Lock。
Lock
Lock介面定義如下
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long timeout,TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}複製程式碼
從介面的定義不難發現,Lock不僅提供了常規的lock()阻塞式加鎖,也提供了tryLock使得執行緒能在獲取不到鎖時,馬上返回,甚至可以等待鎖一段時間後,再返回。lockInterruptibly則提供了可中斷的阻塞式獲取鎖方式。
Lock的鎖需要顯示釋放,通常要與try...finally
語句一起使用,避免死鎖。
lock.lock();
try {
// update object state
// catch exceptions and restore invariants if necessary
} finally {
lock.unlock();
}複製程式碼
ReentrantLock
Lock最常用的實現類是ReentrantLock,這是一個可重入鎖(synchronized也是)。
ReentrantLock預設和內建鎖一樣,是非公平鎖,但是支援公平鎖模式,可以用ReentrantLock(true)
建立公平鎖。
可重入鎖
所謂可重入鎖,也就是說一個執行緒可以在持有該鎖的時候,再次獲取該鎖。可重入鎖通常與一個計數器關聯,第一次獲取鎖的時候,計數器從0變為1,再次獲取鎖,變為2,以此類推。釋放鎖的時候,計數器每次減1,直至減為0,該鎖才真正釋放給其他執行緒。為啥需要可重入鎖舉個例子(JCP書上的)
public class Widget {
public synchronized void doSomething() {
...
}
}
public class LoggingWidget extends Widget {
public synchronized void doSomething() {
System.out.println(toString() + ": calling doSomething");
super.doSomething();
}
}複製程式碼
子類覆蓋了父類方法,並再次呼叫了父類的同步方法,如果鎖不支援重入,則會導致死鎖。
公平鎖
所謂公平鎖,其實就是指鎖的等待佇列執行先進先出,等待最久的執行緒優先獲得鎖。但是內建鎖和ReentrantLock預設都是非公平的,為啥?因為非公平鎖的效能更好。一個事實是,一個執行緒從被喚醒到真正執行中間有不可忽視的延遲,這個延遲時間很可能長到足夠一個執行中的執行緒獲取鎖,並完成操作,然後釋放鎖。也即是說,把鎖給’等待最久的執行緒‘的過程中,可以讓其他執行緒插隊獲取鎖,並歸還鎖,還不會影響’等待最久的執行緒‘的執行。這樣一來吞吐量就得到了提升。
Scala栗子
package io.github.liam8.con
import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.{Lock,ReentrantLock}
object LockDemo {
private val rtl: Lock = new ReentrantLock()
var inc: Int = 0
def get(): Int = {
rtl.lock()
try {
inc
} finally {
rtl.unlock()
}
}
def addOne(): Unit = {
rtl.lock()
try {
TimeUnit.SECONDS.sleep(1)
inc = 1 + get()
} finally {
rtl.unlock()
}
}
def main(args: Array[String]): Unit = {
for (i <- 1 to 10) {
new Thread {
override def run(): Unit = {
println(s"run thread $i")
addOne()
}
}.start()
}
while (true) {
println(s"inc=$inc")
TimeUnit.SECONDS.sleep(1)
}
}
}複製程式碼
output
run thread 3
run thread 8
run thread 1
run thread 9
run thread 7
run thread 4
run thread 5
run thread 2
run thread 10
run thread 6
inc=0
inc=0
inc=2
inc=3
inc=4
inc=5
inc=6
inc=7
inc=8
inc=8
inc=10
inc=10
inc=10複製程式碼
本文程式碼
轉載請註明原文地址:liam-blog.ml/2019/07/21/…檢視更多博主文章