Java有很多種鎖:公平鎖、非公平鎖、可重入鎖、遞歸鎖、自旋鎖、讀鎖、寫(xiě)鎖、等等
公平和非公平鎖
java.util.concurrent.locks.ReentrantLock可以通過(guò)指定構(gòu)造函數(shù)的boolean類(lèi)型來(lái)得到公平鎖或者非公平鎖,默認(rèn)情況下將構(gòu)造非公平鎖。
是什么
公平鎖:是指多個(gè)線程按照申請(qǐng)鎖的順序來(lái)獲取鎖,類(lèi)似于排隊(duì),先來(lái)后到。
非公平鎖:是指多個(gè)線程獲取鎖的順序并不是按照申請(qǐng)鎖的順序,有可能后申請(qǐng)的線程比先申請(qǐng)的線程更有先獲取鎖,在高并發(fā)的情況下,有可能會(huì)造成優(yōu)先級(jí)反轉(zhuǎn)或者饑餓現(xiàn)象。
//Creates an instance of ReentrantLock. This is equivalent to using ReentrantLock(false).
public ReentrantLock() {
sync = new NonfairSync();
構(gòu)造方法有兩個(gè):
ReentrantLock()
ReentrantLock(boolean fair)
根據(jù)給定的公平政策創(chuàng)建一個(gè) ReentrantLock的實(shí)例。(默認(rèn)情況下創(chuàng)建的為非公平鎖)
兩者之間的區(qū)別
公平鎖:并發(fā)環(huán)境下,每個(gè)線程獲取鎖時(shí)會(huì)先查看此鎖維護(hù)的等待隊(duì)列,如果為空,或者當(dāng)前線程是等待隊(duì)列的一個(gè),就占有鎖,否則就睡加入到等待隊(duì)列中,以后會(huì)按照FIFO規(guī)則從隊(duì)列中取到自己。
非公平鎖:比較粗魯,一開(kāi)始就直接嘗試占有鎖,如果嘗試失敗,再采用類(lèi)似公平鎖的方式。
- 非公平鎖的優(yōu)點(diǎn)在于吞吐量比公平鎖大
- 對(duì)于
Synchronized而言,也是非公平鎖。
-------------------------------------------(分割線)--------------------------------------------------
可重入鎖(又名遞歸鎖)(ReentrantLock)
是什么
可重入鎖,也叫遞歸鎖。指的是
- 同一線程外層函數(shù)獲得鎖之后,內(nèi)層遞歸函數(shù)仍能獲得該鎖的代碼。
- 在同一個(gè)線程在外層方法獲取鎖的時(shí)候,再進(jìn)入內(nèi)層方法會(huì)自動(dòng)獲取鎖。
即: 線程可以進(jìn)入任何一個(gè)他已經(jīng)擁有的鎖所同步著的代碼塊。
//外層函數(shù)
public synchronized void method01(){
method02();//M2的鎖由于M1 獲得了鎖而自然獲得
}
//內(nèi)層函數(shù)
public synchronized void method02(){}
ReentrantLock/Synchronize都是典型的可重入鎖(默認(rèn)非公平)
可重入鎖最大的作用就是避免死鎖
死鎖,M1獲得了鎖,調(diào)用M2,M2一直等待M1結(jié)束,M1會(huì)死鎖
一系列流程使用同一個(gè)鎖,執(zhí)行方法1時(shí)候鎖住,執(zhí)行方法2時(shí)需要等待自己在方法1 中獲得的鎖解掉,形成了自己需要獲得鎖,自己又需要解掉鎖的死鎖情況。
可重入鎖的種類(lèi)
- 隱式鎖(即
Synchronized關(guān)鍵字使用的鎖)默認(rèn)是可重入鎖 - 顯式鎖(即
Lock)也有ReentrantLock這樣的可重入鎖
ReentrantLockDemo
class Phone implements Runnable
{
public synchronized void sendSMS()throws Exception{
System.out.println(Thread.currentThread().getName()+"\t-----------invoked sendSMS");
sendEmail();
}
public synchronized void sendEmail()throws Exception{
System.out.println(Thread.currentThread().getName()+"\t+++++++++++invoked sendEmail");
}
//----------------------------------------------------------------------
Lock lock = new ReentrantLock();
@Override
public void run() {
get();
}
private void get(){
//加鎖要和解鎖個(gè)數(shù)配對(duì)?。?編譯運(yùn)行都不會(huì)報(bào)錯(cuò),會(huì)有鎖滯留)
lock.lock();
try{
System.out.println(Thread.currentThread().getName()+"\t-----------invoked get");
set();
}finally {
//加鎖要和解鎖個(gè)數(shù)配對(duì)?。?編譯運(yùn)行都不會(huì)報(bào)錯(cuò),會(huì)有鎖滯留)
lock.unlock();
}
}
private void set() {
lock.lock();
try{
System.out.println(Thread.currentThread().getName()+"\t+++++++++++invoked set");
}finally {
lock.unlock();
}
}
}
public class ReentrantLockDemo {
public static void main(String[] args) {
System.out.println("-----------------------Synchronized");
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();
try{TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
System.out.println("-----------------------ReentrantLock");
new Thread(()->{
phone.run();
},"t3").start();
new Thread(()->{
phone.run();
},"t4").start();
}
}
Console:
-----------------------Synchronized
t1 -----------invoked sendSMS
t1 +++++++++++invoked sendEmail
t2 -----------invoked sendSMS
t2 +++++++++++invoked sendEmail
-----------------------ReentrantLock
t3 -----------invoked get
t3 +++++++++++invoked set
t4 -----------invoked get
t4 +++++++++++invoked set
加鎖要和解鎖個(gè)數(shù)配對(duì)!!(編譯運(yùn)行都不會(huì)報(bào)錯(cuò),會(huì)有鎖滯留)
自旋鎖
是指嘗試獲取鎖的線程不會(huì)立即阻塞,而是采用循環(huán)的方式取嘗試獲取鎖,
- 這樣做的好處是減少線程上下文切換的消耗
- 缺點(diǎn)是循環(huán)會(huì)消耗CPU。
- 循環(huán)比較獲取,直到成功為止,沒(méi)有類(lèi)似wait的阻塞。
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));
//預(yù)期值和主物理內(nèi)存中的值不一致就一直重新取(可能會(huì)出現(xiàn)多次循環(huán))
return var5;
}
手寫(xiě)一個(gè)自旋鎖
package com.company;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
public class SpinLockDemo {
//原子引用線程
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(thread.getName()+" came in for the Lock");
while (!atomicReference.compareAndSet(null,thread)){
//空循環(huán)-> 直到atomicReference為null時(shí),執(zhí)行CAS,并跳出循環(huán)
}
}
public void unLock(){
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread,null);
System.out.println(thread.getName()+" release the Lock");
}
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(()->{
spinLockDemo.myLock();
System.out.println("Thread A get the Lock");
try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}
spinLockDemo.unLock();
},"A").start();
try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
new Thread(()->{
spinLockDemo.myLock();
System.out.println("Thread B get the Lock");
spinLockDemo.unLock();
},"B").start();
}
}
----------------------------------------------------------------------------------------------------
Console:
A came in for the Lock
Thread A get the Lock
B came in for the Lock
(5S后)
A release the Lock
Thread B get the Lock
B release the Lock