公平鎖、非公平鎖
公平鎖:是指多個線程按照申請鎖的順序來獲取鎖,類似排隊,先來後到。
非公平鎖:是指多個線程獲取鎖的順序並不是按照申請鎖的順序,有可能後申請的線程比先申請的線程優先獲取鎖。在高併發的情況下,有可能會造成優先級反轉或者飢餓現象。
在Java併發包中ReentrantLock的創建可以指定構造函數的boolean類型來得到公平鎖或非公平鎖,默認是非公平鎖。
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
通過構造函數指定該鎖是否是公平鎖,默認是非公平鎖
。非公平鎖的優點在於吞吐量比公平鎖大。
synchronized也是一種非公平鎖。
區別:
公平鎖:公平鎖就是很公平,在併發環境中,每個線程在獲取鎖時會先查看此鎖維護的等待隊列,如果爲空,獲取當前線程是等待隊列的第一個,就佔有鎖,否則就會加入到等待隊列中,以後會按照FIFO的規則從隊列中渠道自己。
非公平鎖:非公平鎖比較粗魯,上來就直接嘗試佔有鎖,如果嘗試失敗,就再採用類似公平鎖那種方式。
尚硅谷大數據全套視頻教程40階段(2019.6月線下班)
可重入鎖(遞歸鎖)
可重入鎖(也叫做遞歸鎖),指的是同一線程外層函數獲得鎖之後,內層遞歸函數仍然能獲取該鎖的代碼。同一個線程在外層方法獲取到鎖的時候,在進入內層方法會自動獲取鎖。
也就是說,線程可以進入任何一個它已經擁有的鎖所同步的代碼塊。
ReentrantLock、synchronized 就是一個典型的可重入鎖,可重入鎖最大作用是避免死鎖
。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Foo {
private Lock lock = new ReentrantLock();
public void print1() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + ", print1()");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.print2();
}finally {
lock.unlock();
}
}
private void print2() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + ", print2()");
}finally {
lock.unlock();
}
}
}
public class ReentrantLockDemo {
public static void main(String[] args) throws InterruptedException {
Foo foo = new Foo();
Thread t1 = new Thread(()-> {
foo.print1();
}, "thread-1");
Thread t2 = new Thread(()-> {
foo.print1();
}, "thread-2");
t1.start();
t2.start();
// main方法等待t1 t2線程執行結束
t1.join();
t2.join();
}
}
自旋鎖
自旋鎖(spinlock)是指嘗試獲取鎖的線程不會立即阻塞,而是採用循環的方式去嘗試獲取鎖,這樣的好處是減少線程上下文切換的消耗,缺點是循環會消耗CPU。
Unsafe類中的getAndAddInt方法
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
手寫自旋鎖
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
class MySpinLock {
private AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void lock() {
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + " 正在獲取鎖");
while (!atomicReference.compareAndSet(null, thread)) {
}
System.out.println(thread.getName() + " 獲取到鎖");
}
public void unLock() {
Thread thread = Thread.currentThread();
boolean result = atomicReference.compareAndSet(thread, null);
System.out.println(thread.getName() + " 釋放鎖 result = " + result);
}
}
public class SpinLockDemo {
public static void main(String[] args) throws InterruptedException {
MySpinLock lock = new MySpinLock();
Thread t1 = new Thread(()-> {
lock.lock();
System.out.println(Thread.currentThread().getName() + " do something");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unLock();
}, "t1");
t1.start();
TimeUnit.SECONDS.sleep(1);
Thread t2 = new Thread(()-> {
lock.lock();
System.out.println(Thread.currentThread().getName() + " do something");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unLock();
}, "t2");
t2.start();
t1.join();
t2.join();
}
}
獨佔鎖、共享鎖
獨佔鎖:指該鎖一次只能被一個線程持有,ReentrantLock和synchronized 都是獨佔鎖。
共享鎖:指該鎖可被多個線程所持有。
ReentrantReadWriteLock 其讀鎖是共享鎖,寫鎖是獨佔鎖。
讀鎖的共享鎖可保證併發讀是非常高效的,讀寫、寫讀、寫寫的過程是互斥的。