Java多線程與併發_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();
}
兩者區別
-
公平鎖:就是很公平,在併發環境中,每個線程在獲取鎖時會先查看此鎖維護的等待隊列,如果爲空,或者當前線程是等待隊列的第一個,就佔優鎖,否則就會加入到等待隊列中,以後會按照FIFO的規則從隊列中取到自己
-
非公平鎖:非公平鎖不比較粗魯,上來就直接嘗試佔有鎖,如果嘗試失敗,就再採用類似公平鎖那種方式
二、可重入鎖(遞歸鎖)
指的是同一線程外層函數獲得鎖之後,內層遞歸函數仍然能獲取該鎖的代碼,在同一個線程在外層方法獲取鎖的時候,在進入內層方法會自動獲取鎖
也就是說,線程可以進入任何一個它已經擁有的鎖所同步着的代碼塊
class Phone implements Runnable{
public synchronized void sendSMS() {
System.out.println(Thread.currentThread().getName() + "\t invoked sendSMS()");
sendEmail();
}
public synchronized void sendEmail() {
System.out.println(Thread.currentThread().getName() + "\t ########invoked sendEmail()");
}
Lock lock = new ReentrantLock();
@Override
public void run() {
get();
}
public void get() {
lock.lock();
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t invoked get()");
set();
}catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
lock.unlock();
}
}
public void set() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t invoked set()");
}catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
Phone phone = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
}catch (Exception e) {
e.printStackTrace();
}
},"t1").start();
new Thread(() -> {
try {
phone.sendSMS();
}catch (Exception e) {
e.printStackTrace();
}
},"t2").start();
sychoronized可重入鎖結果:
t1 invoked sendSMS() t1線程在外層方法獲取鎖的時候
t1 ########invoked sendEmail() t1在進入內層方法會自動獲取鎖
t2 invoked sendSMS()
t2 ########invoked sendEmail()
Process finished with exit code 0
Thread t3 = new Thread(phone,"t3");
Thread t4 = new Thread(phone,"t4");
t3.start();
t4.start();
ReentrantLock可重入鎖結果:
t3 invoked get()
t3 invoked set()
t4 invoked get()
t4 invoked set()
Process finished with exit code 0
三、自旋鎖
指嘗試獲取鎖的線程不會立即阻塞,而是採用循環的方式去嘗試獲取鎖,這樣的好處是減少線程上下文的消耗,缺點是循環會消耗CPU
實現一個自旋鎖
自旋鎖好處:循環比較獲取直到成功爲止,沒有類似wait的阻塞
通過CAS操作完成自旋鎖,A線程先進來調用myLock方法自己持有鎖5秒,B隨後進來後發現
當前有線程持有鎖,不是null,所以只能通過自旋等待,直到A釋放鎖後B隨後搶到
public class SpinLockDemo {
//原子引用線程
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void myLock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "\t come in");
while(!atomicReference.compareAndSet(null,thread)) {
}
}
public void myUnLock() {
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread,null);
System.out.println(Thread.currentThread().getName() + "\t invoked myUnLock()");
}
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(() -> {
spinLockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.myUnLock();
},"AA").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
spinLockDemo.myLock();
spinLockDemo.myUnLock();
},"BB").start();
}
}
結果
AA come in
BB come in
AA invoked myUnLock()
BB invoked myUnLock()
Process finished with exit code 0