18. JUC談談你對公平鎖/非公平鎖/可重入鎖/遞迴鎖/自旋鎖的理解
阿新 • • 發佈:2021-01-15
公平和非公平鎖
-
公平鎖是指多個執行緒按照申請鎖的順序來獲取鎖類似排隊,先來後到
-
非公平鎖是指,在高併發的情況下,有可能後面的執行緒比前面的執行緒先得到鎖,多個執行緒獲取鎖的順序並不按照申請鎖的順序,這就可能造成優先順序反轉或者飢餓現象
兩者的區別
併發包ReentrantLock的建立,可以指定建構函式的boolean型別來得到公平鎖或者非公平鎖,預設是非公平鎖
公平鎖,就是很公平,在併發環境下,每個執行緒在獲取鎖時會檢視此鎖維護的等待佇列,如果為空,或者當前執行緒是等待佇列的第一個,
就佔有鎖,否則就會加入到等待佇列中,以後會按照FIFO的規則從佇列中取到自己
非公平鎖,比較粗魯,上來就嘗試直接佔有鎖,如果嘗試失敗,就再採用類似公平鎖那種方式。
題外話
-
對於ReentrantLock而言,通過建構函式指定該鎖是否為公平鎖,預設是false,非公平鎖,非公平鎖的優點在於吞吐量比公平鎖大.
-
synchronized也是一種非公平鎖
可重入鎖
可重入鎖也叫做遞迴鎖,指的是同一執行緒,外層函式獲得鎖之後,內層遞迴函式仍然能獲取該鎖的程式碼。
ReentrantLock/synchronized就是典型的可重入鎖,可重入鎖最大的作用就是避免死鎖
使用synchronized實現
public class ReentrantLockDemo{
public static void main(String[] args){
Phone p = new Phone();
new Thread(() -> {
p.sendEms();
},"t1").start();
new Thread(() -> {
p.sendEms();
},"t2").start();
}
}
class Phone{
public synchronized void sendEms(){
System.out.println(Thread.currentThread( ).getName() + "\tsendEms");
sendEmail();
}
public synchronized void sendEmail(){
System.out.println(Thread.currentThread().getName() + "\tsendEmail");
}
}
使用RenentrantLock實現
public class ReentrantLockDemo{
public static void main(String[] args){
Phone p = new Phone();
Thread t3 = new Thread(p,"t3");
Thread t4 = new Thread(p,"t4");
t3.start();
t4.start();
}
}
class Phone implements Runnable{
Lock l = new ReentrantLock();
@Override
public void run(){
get();
}
public void get(){
l.lock();
try{
System.out.println(Thread.currentThread().getName() + "\tget");
set();
}finally{
l.unlock();
}
}
public void set(){
l.lock();
try{
System.out.println(Thread.currentThread().getName() + "\tset");
}finally{
l.unlock();
}
}
}
自旋鎖
是指嘗試獲取鎖的執行緒不會立即阻塞,而是採用迴圈的方式獲取鎖,這樣的好處是減少執行緒上下文切換的消耗,缺點是迴圈會消耗CPU
手寫一個自旋鎖
public class SpinLockDemo {
public static void main(String[] args) {
SpinLock s = new SpinLock();
new Thread(() -> {
s.myLock();
// 模擬A執行緒佔用鎖5秒鐘
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
s.myUnlock();
}, "A").start();
// 暫停1秒鐘,保證A執行緒先執行
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
// B執行緒在A執行緒持有鎖的這5秒期間,會一直迴圈訪問comepareAndSet,
// 直到A執行緒解鎖之後,B執行緒才會獲得鎖,然後1秒之後B執行緒解鎖
s.myLock();
// 暫停1秒鐘,使結果清晰
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
s.myUnlock();
}, "B").start();
}
}
class SpinLock {
AtomicReference<Thread> ar = new AtomicReference<>();
public void myLock() {
Thread thread = Thread.currentThread();
// 如果是第一個執行緒進來,則為其設定上鎖,結果返回true,退出迴圈
// 如果不是第一個執行緒進來,則不會為其上鎖,結果會返回false,一直迴圈
System.out.println(thread.getName() + "\t進來了");
while (!ar.compareAndSet(null, thread)) {
}
System.out.println(thread.getName() + "\t拿到鎖了");
}
public void myUnlock() {
Thread thread = Thread.currentThread();
// 當前執行緒使用完鎖之後,為其解鎖
ar.compareAndSet(thread, null);
System.out.println(thread.getName() + "\t解鎖了");
}
}