詳解Java中的鎖Lock和synchronized
一、Lock接口
1、Lock接口和synchronized內(nèi)置鎖
a)synchronized:Java提供的內(nèi)置鎖機(jī)制,Java中的每個(gè)對(duì)象都可以用作一個(gè)實(shí)現(xiàn)同步的鎖(內(nèi)置鎖或者監(jiān)視器Monitor),線程在進(jìn)入同步代碼塊之前需要或者這把鎖,在退出同步代碼塊會(huì)釋放鎖。而synchronized這種內(nèi)置鎖實(shí)際上是互斥的,即沒把鎖最多只能由一個(gè)線程持有。
b)Lock接口:Lock接口提供了與synchronized相似的同步功能,和synchronized(隱式的獲取和釋放鎖,主要體現(xiàn)在線程進(jìn)入同步代碼塊之前需要獲取鎖退出同步代碼塊需要釋放鎖)不同的是,Lock在使用的時(shí)候是顯示的獲取和釋放鎖。雖然Lock接口缺少了synchronized隱式獲取釋放鎖的便捷性,但是對(duì)于鎖的操作具有更強(qiáng)的可操作性、可控制性以及提供可中斷操作和超時(shí)獲取鎖等機(jī)制。
2、lock接口使用的一般形式
Lock lock = new ReentrantLock(); //這里可以是自己實(shí)現(xiàn)Lock接口的實(shí)現(xiàn)類,也可以是jdk提供的同步組件 lock.lock();//一般不將鎖的獲取放在try語句塊中,因?yàn)槿绻l(fā)生異常,在拋出異常的同時(shí),也會(huì)導(dǎo)致鎖的無故釋放 try { }finally { lock.unlock(); //放在finally代碼塊中,保證鎖一定會(huì)被釋放 }
3、Lock接口的方法
public interface Lock { /** * 獲取鎖,調(diào)用該方法的線程會(huì)獲取鎖,當(dāng)獲取到鎖之后會(huì)從該方法但會(huì) */ void lock(); /** * 可響應(yīng)中斷。即在獲取鎖的過程中可以中斷當(dāng)前線程 */ void lockInterruptibly() throws InterruptedException; /** * 嘗試非阻塞的獲取鎖,調(diào)用該方法之后會(huì)立即返回,如果獲取到鎖就返回true否則返回false */ boolean tryLock(); /** * 超時(shí)的獲取鎖,下面的三種情況會(huì)返回 * ①當(dāng)前線程在超時(shí)時(shí)間內(nèi)獲取到了鎖 * ②當(dāng)前線程在超時(shí)時(shí)間內(nèi)被中斷 * ③超時(shí)時(shí)間結(jié)束,返回false */ boolean tryLock(long time, TimeUnit unit) throws InterruptedException; /** * 釋放鎖 */ void unlock(); /** * 獲取等待通知組件,該組件和當(dāng)前鎖綁定,當(dāng)前線程只有獲取到了鎖才能調(diào)用組件的wait方法,調(diào)用該方法之后會(huì)釋放鎖 */ Condition newCondition(); }
4、相比于synchronized,Lock接口所具備的其他特性
①嘗試非阻塞的獲取鎖tryLock():當(dāng)前線程嘗試獲取鎖,如果該時(shí)刻鎖沒有被其他線程獲取到,就能成功獲取并持有鎖
②能被中斷的獲取鎖lockInterruptibly():獲取到鎖的線程能夠響應(yīng)中斷,當(dāng)獲取到鎖的線程被中斷的時(shí)候,會(huì)拋出中斷異常同時(shí)釋放持有的鎖
③超時(shí)的獲取鎖tryLock(long time, TimeUnit unit):在指定的截止時(shí)間獲取鎖,如果沒有獲取到鎖返回false
二、重入鎖
1、重入鎖的概念
當(dāng)某個(gè)線程請(qǐng)求一個(gè)被其他線程所持有的鎖的時(shí)候,該線程會(huì)被阻塞(后面的讀寫鎖先不考慮在內(nèi)),但是像synchronized這樣的內(nèi)置鎖是可重入的,即一個(gè)線程試圖獲取一個(gè)已經(jīng)被該線程所持有的鎖,這個(gè)請(qǐng)求會(huì)成功。重入以為這鎖的操作粒度是線程級(jí)別而不是調(diào)用級(jí)別。我們下面說到的ReentrantLock也是可重入的,而除了支持鎖的重入之外,該同步組件也支持公平的和非公平的選擇。
2、ReentrantLock
a)ReentrantLock實(shí)現(xiàn)的可重入性
對(duì)于鎖的可重入性,需要解決的兩個(gè)問題就是:
①線程再次獲取鎖的識(shí)別問題(鎖需要識(shí)別當(dāng)前要獲取鎖的線程是否為當(dāng)前占有鎖的線程);
②鎖的釋放(同一個(gè)線程多次獲取同一把鎖,那么鎖的記錄也會(huì)不同。一般來說,當(dāng)同一個(gè)線程重復(fù)n次獲取鎖之后,只有在之后的釋放n次鎖之后,其他的線程才能去競(jìng)爭(zhēng)這把鎖)
③ReentrantLock的可重入測(cè)試
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TestCR { Lock lock = new ReentrantLock(); void m1(){ try{ lock.lock(); // 加鎖 for(int i = 0; i < 4; i++){ TimeUnit.SECONDS.sleep(1); System.out.println("m1() method " + i); } m2(); //在釋放鎖之前,調(diào)用m2方法 }catch(InterruptedException e){ e.printStackTrace(); }finally{ lock.unlock(); // 解鎖 } } void m2(){ lock.lock(); System.out.println("m2() method"); lock.unlock(); } public static void main(String[] args) { final TestCR t = new TestCR(); new Thread(new Runnable() { @Override public void run() { t.m1(); } }).start(); new Thread(new Runnable() { @Override public void run() { t.m2(); } }).start(); } }
b)下面分析ReentrantLock的部分源碼來學(xué)習(xí)這個(gè)同步組件(默認(rèn)的非公平鎖實(shí)現(xiàn))
①首先可以知道ReentrantLock實(shí)現(xiàn)Lock接口public class ReentrantLock implements Lock
abstract static class Sync extends AbstractQueuedSynchronizer { /** * 創(chuàng)建非公平鎖的方法 */ abstract void lock(); /** * 執(zhí)行非公平的tryLock。 tryAcquire實(shí)現(xiàn)于 * 子類,但兩者都需要tryf方法的非公平嘗試。 */ final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread();//獲取當(dāng)前線程 int c = getState(); //獲取當(dāng)前同步狀態(tài)的值 if (c == 0) { //如果當(dāng)前的同步狀態(tài)還沒有被任何線程獲取 if (compareAndSetState(0, acquires)) { //就更新同步狀態(tài)的值,因?yàn)橐呀?jīng)有線程獲取到同步裝填 setExclusiveOwnerThread(current);//設(shè)置同步狀態(tài)的線程擁有者為當(dāng)前獲取的線程 return true; } } else if (current == getExclusiveOwnerThread()) {//增加再次獲取同步狀態(tài)的處理邏輯 int nextc = c + acquires; //如果再次嘗試獲取同步狀態(tài)的線程就是當(dāng)前已經(jīng)占有同步狀態(tài)的線程,那么就更新同步狀態(tài)的值(進(jìn)行增加操作) if (nextc < 0) // 對(duì)同步狀態(tài)的值進(jìn)行非法判斷 throw new Error("Maximum lock count exceeded"); setState(nextc); //更新state的值 return true; } return false; } /** * 釋放同步狀態(tài)的處理邏輯 */ protected final boolean tryRelease(int releases) { int c = getState() - releases; //對(duì)同一線程而言,就是減去相應(yīng)的獲取次數(shù) if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; //返回值 if (c == 0) { //只有該線程將獲取的次數(shù)全部釋放之后,才會(huì)返回true,并且將當(dāng)前同步狀態(tài)的持有者設(shè)置為null free = true; setExclusiveOwnerThread(null); } setState(c); //更新state return free; } /** * 判斷當(dāng)前同步狀態(tài)的持有者線程 */ protected final boolean isHeldExclusively() { return getExclusiveOwnerThread() == Thread.currentThread(); } final ConditionObject newCondition() { return new ConditionObject(); } /** * 返回當(dāng)前持有者線程 */ final Thread getOwner() { return getState() == 0 ? null : getExclusiveOwnerThread(); } /** * 返回持有同步狀態(tài)的線程獲取次數(shù) */ final int getHoldCount() { return isHeldExclusively() ? getState() : 0; } /** * 判斷當(dāng)前是否有線程獲取到同步狀態(tài)(根據(jù)state值進(jìn)行判斷) */ final boolean isLocked() { return getState() != 0; } private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); setState(0); // reset to unlocked state } }
②通過上面的非公平鎖的實(shí)現(xiàn)源碼可以看到,ReentrantLock實(shí)現(xiàn)可重入的邏輯大概上是這樣的:
獲取邏輯:首先通過nonfairTryAcquire方法增加了對(duì)于同一線程再次獲取同步狀態(tài)的邏輯處理(通過判斷當(dāng)前線程是否為已經(jīng)同步狀態(tài)的持有者,來決定是否能夠再次獲取同步狀態(tài),如果當(dāng)前線程是已經(jīng)獲取到同步狀態(tài)的那個(gè)線程,那么就能夠獲取成功,并且同時(shí)以CAS的方式修改state的值)
釋放邏輯:對(duì)于成功獲取到同步狀態(tài)的線程,在釋放鎖的時(shí)候,通過tryRelease方法的實(shí)現(xiàn)可以看出,如果該鎖被線程獲取到了n次,那么前(n-1)次釋放的操作都會(huì)返回false,只有將同步狀態(tài)完全釋放才會(huì)返回true。最終獲取到同步狀態(tài)的線程在完全釋放掉之后,state值為0并且持有鎖的線程為null。
c)關(guān)于ReentrantLock的公平和非公平實(shí)現(xiàn)
①非公平鎖
公平和非公平是針對(duì)于獲取鎖而言的,對(duì)于公平鎖而言獲取鎖應(yīng)該遵循FIFO原則,上面我們通過源碼分析了非公平鎖的實(shí)現(xiàn)(對(duì)于非公平鎖而言,tryAcquire方法直接使用的是ReentrantLock靜態(tài)內(nèi)部類Sync的nofairTryAcquire方法)
//非公平鎖實(shí)現(xiàn) static final class NonfairSync extends Sync { /** * 以CAS方式原子的更新state的值 */ final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } /** * 非公平鎖的實(shí)現(xiàn)是直接調(diào)用Sync的nonfairTryAcquire方法 */ protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } }
②公平鎖實(shí)現(xiàn)
公平鎖的實(shí)現(xiàn)和非公平實(shí)現(xiàn)的主要區(qū)別就是tryAcquire方法的實(shí)現(xiàn)
static final class FairSync extends Sync { final void lock() { acquire(1); //調(diào)用AQS的模板方法實(shí)現(xiàn)鎖的獲取 } /** * 公平鎖的處理邏輯 */ protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); //獲取當(dāng)前線程 int c = getState(); //獲取當(dāng)前同步狀態(tài)的值 if (c == 0) { //當(dāng)前同步狀態(tài)沒有被任何線程獲取的時(shí)候 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { //這個(gè)點(diǎn)的主要處理邏輯就是:hasQueuedPredecessors判斷當(dāng)前線程所在的結(jié)點(diǎn)是否含有前驅(qū)結(jié)點(diǎn), 如果返回值為true表示有前驅(qū)結(jié)點(diǎn),那么當(dāng)前線程需要等待前驅(qū)結(jié)點(diǎn)中的線程獲取并釋放鎖之后才能獲取鎖,保證了FIFO setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { //支持重入的邏輯,和非公平鎖的實(shí)現(xiàn)原理相同 int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } } //hasQueuedPredecessors的處理邏輯 public final boolean hasQueuedPredecessors() { // 簡(jiǎn)單而言,就是判斷當(dāng)前線程是否有前驅(qū)結(jié)點(diǎn) // 當(dāng)前結(jié)點(diǎn)含有前驅(qū)結(jié)點(diǎn)時(shí)候返回true;當(dāng)前結(jié)點(diǎn)為頭結(jié)點(diǎn)揮著隊(duì)列為空的時(shí)候返回false Node t = tail; // Read fields in reverse initialization order Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }
d)公平鎖和非公平鎖的測(cè)試
①測(cè)試目的
驗(yàn)證上面通過源碼分析的,非公平鎖在獲取鎖的時(shí)候會(huì)首先進(jìn)行搶鎖,在獲取鎖失敗后才會(huì)將當(dāng)前線程加入同步隊(duì)列隊(duì)尾中,而公平鎖則是符合請(qǐng)求的絕對(duì)順序,也就是會(huì)按照先來后到FIFO。在下面的代碼中我們使用一個(gè)靜態(tài)內(nèi)部類繼承了ReentrantLock并重寫等待隊(duì)列的方法,作為測(cè)試的ReentrantLock。然后創(chuàng)建5個(gè)線程,每個(gè)線程連續(xù)兩次去獲取鎖,分別測(cè)試公平鎖和非公平鎖的測(cè)試結(jié)果
import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.junit.Test; public class TestReentrantLock { /** * ReentrantLock的構(gòu)造方法 * public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();} */ private Lock fairLock = new ReentrantLock2(true); private Lock unFairLock = new ReentrantLock2(false); @Test public void testFair() throws InterruptedException { testLock(fairLock); //測(cè)試公平鎖 } @Test public void testUnFair() throws InterruptedException { testLock(unFairLock); //測(cè)試非公平鎖 } private void testLock(Lock lock) throws InterruptedException { for (int i = 0; i < 5; i++) { Thread thread = new Thread(new Job(lock)) { public String toString() { return getName(); } }; thread.setName(i+""); thread.start(); } Thread.sleep(12000); } private static class Job extends Thread { private Lock lock; public Job(Lock lock) { this.lock = lock; } @Override public void run() { //兩次打印當(dāng)前線程和等待隊(duì)列中的Threads for (int i = 0; i < 2; i++) { lock.lock(); //獲取鎖 try { Thread.sleep(1000); System.out.println("當(dāng)前線程=>" + Thread.currentThread().getName() + " " + "等待隊(duì)列中的線程=>" + ((ReentrantLock2)lock).getQueuedThreads()); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); //釋放鎖 } } } } private static class ReentrantLock2 extends ReentrantLock { public ReentrantLock2(boolean fair) { super(fair); } public Collection<Thread> getQueuedThreads() { //逆序打印等待隊(duì)列中的線程 List<Thread> list = new ArrayList<Thread>(super.getQueuedThreads()); Collections.reverse(list); return list; } } }
②測(cè)試非公平鎖
由上面的測(cè)試結(jié)果簡(jiǎn)單的得到關(guān)于非公平鎖的一個(gè)結(jié)論:通過nofairTryAcquire方法可以得到這樣一個(gè)前提,當(dāng)一個(gè)線程請(qǐng)求一個(gè)鎖時(shí),判斷獲取成功的條件就是這個(gè)線程獲取到同步狀態(tài)就可以,那么某個(gè)剛剛釋放鎖的線程再次獲取到同步狀態(tài)的幾率就會(huì)更大一些(當(dāng)然實(shí)驗(yàn)中也出現(xiàn)并非連續(xù)兩次獲取這把鎖的情況,比如下面的測(cè)試結(jié)果)
③測(cè)試公平鎖
通過分析下面的測(cè)試結(jié)果,對(duì)于使用公平鎖而言,即便是同一個(gè)線程連續(xù)兩次獲取鎖釋放鎖,在第一次釋放鎖之后還是會(huì)被放在隊(duì)尾并從隊(duì)列頭部拿出線程進(jìn)行執(zhí)行。并沒有出現(xiàn)像非公平鎖那樣連續(xù)兩次獲取鎖的那種情況
④由上面的測(cè)試可以看出:非公平鎖可能導(dǎo)致在隊(duì)尾的線程饑餓,但是又因?yàn)橥粋€(gè)線程在釋放鎖的時(shí)候有更大的概率再次獲取到這把鎖,那么這樣的話線程的切換次數(shù)就會(huì)更少(這帶來的就是更大的吞吐量和開銷的減?。?。而雖然公平鎖的獲取嚴(yán)格按照FIFO的規(guī)則,但是線程切換的次數(shù)就會(huì)更多。
三、Synchronized
1、Synchronized作用對(duì)象
①對(duì)于普通方法,鎖的是當(dāng)前實(shí)例對(duì)象
②對(duì)于靜態(tài)同步方法,鎖的是類的Class對(duì)象
③對(duì)于同步代碼塊,鎖的是Synchronized括號(hào)中的對(duì)象
如下所示的三種情況
package cn.source.sync; public class TestSync01 { private static int count = 0; private Object object = new Object(); public void testSyn1() { //同步代碼塊(這里面是鎖臨界資源,即括號(hào)中的對(duì)象) synchronized (object) { System.out.println(Thread.currentThread().getName() +" count =" + count++); } } public void testSyn2() { //鎖當(dāng)前對(duì)象(相當(dāng)于普通同步方法) synchronized (this) { System.out.println(Thread.currentThread().getName() +" count =" + count++); } } //普通同步方法:鎖當(dāng)前對(duì)象 public synchronized void testSyn3() { System.out.println(Thread.currentThread().getName() +" count =" + count++); } //靜態(tài)同步方法,鎖的是當(dāng)前類型的類對(duì)象(即TestSync01.class) public static synchronized void testSyn4() { System.out.println(Thread.currentThread().getName() +" count =" + count++); } //下面的這種方式也是鎖當(dāng)前類型的類對(duì)象 public static void testSyn5() { synchronized (TestSync01.class) { System.out.println(Thread.currentThread().getName() +" count =" + count ++); } } }
2、synchronized的實(shí)現(xiàn)原理
①Java 虛擬機(jī)中的同步(Synchronization)基于進(jìn)入和退出管程(Monitor)對(duì)象實(shí)現(xiàn)。同步代碼塊是使用monitorenter和monitorexit來實(shí)現(xiàn)的,同步方法 并不是由 monitor enter 和 monitor exit 指令來實(shí)現(xiàn)同步的,而是由方法調(diào)用指令讀取運(yùn)行時(shí)常量池中方法的 ACC_SYNCHRONIZED 標(biāo)志來隱式實(shí)現(xiàn)的。monitorenter指令是在編譯后插入同步代碼塊的起始位置,而monitorexit指令是在方法結(jié)束處和異常處,每個(gè)對(duì)象都有一個(gè)monitor與之關(guān)聯(lián),當(dāng)一個(gè)monitor被持有后它就會(huì)處于鎖定狀態(tài)。
②synchronized用的鎖是存在Java對(duì)象頭(非數(shù)組類型包括Mark Word、類型指針,數(shù)組類型多了數(shù)組長(zhǎng)度)里面的,對(duì)象頭中的Mark Word存儲(chǔ)對(duì)象的hashCode,分代年齡和鎖標(biāo)記位,類型指針指向?qū)ο蟮脑獢?shù)據(jù)信息,JVM通過這個(gè)指針確定該對(duì)象是那個(gè)類的實(shí)例等信息。
③當(dāng)在對(duì)象上加鎖的時(shí)候,數(shù)據(jù)是記錄在對(duì)象頭中,對(duì)象頭中的Mark Word里存儲(chǔ)的數(shù)據(jù)會(huì)隨著鎖標(biāo)志位的變化而變化(無鎖、輕量級(jí)鎖00、重量級(jí)鎖10、偏向鎖01)。當(dāng)執(zhí)行synchronized的同步方法或者同步代碼塊時(shí)候會(huì)在對(duì)象頭中記錄鎖標(biāo)記,鎖標(biāo)記指向的是monitor對(duì)象(也稱為管程或者監(jiān)視器鎖)的起始地址。由于每個(gè)對(duì)象都有一個(gè)monitor與之關(guān)聯(lián),monitor和與關(guān)聯(lián)的對(duì)象一起創(chuàng)建(當(dāng)線程試圖獲取鎖的時(shí)候)或銷毀,當(dāng)monitor被某個(gè)線程持有之后,就處于鎖定狀態(tài)。
④Hotspot虛擬機(jī)中的實(shí)現(xiàn),通過ObjectMonitor來實(shí)現(xiàn)的
如圖所示,ObjectMonitor中有兩個(gè)隊(duì)列(EntryList、WaitSet)以及鎖持有者Owner標(biāo)記,其中WaitSet是哪些調(diào)用wait方法之后被阻塞等待的線程隊(duì)列,EntryList是ContentionList中能有資格獲取鎖的線程隊(duì)列。當(dāng)多個(gè)線程并發(fā)訪問同一個(gè)同步代碼時(shí)候,首先會(huì)進(jìn)入EntryList,當(dāng)線程獲得鎖之后monitor中的Owner標(biāo)記會(huì)記錄此線程,并在該monitor中的計(jì)數(shù)器執(zhí)行遞增計(jì)算代表當(dāng)前鎖被持有鎖定,而沒有獲取到的線程繼續(xù)在EntryList中阻塞等待。如果線程調(diào)用了wait方法,則monitor中的計(jì)數(shù)器執(zhí)行賦0運(yùn)算,并且將Owner標(biāo)記賦值為null,代表當(dāng)前沒有線程持有鎖,同時(shí)調(diào)用wait方法的線程進(jìn)入WaitSet隊(duì)列中阻塞等待,直到持有鎖的執(zhí)行線程調(diào)用notify/notifyAll方法喚醒WaitSet中的線程,喚醒的線程進(jìn)入EntryList中等待鎖的獲取。除了使用wait方法可以將修改monitor的狀態(tài)之外,顯然持有鎖的線程的同步代碼塊執(zhí)行結(jié)束也會(huì)釋放鎖標(biāo)記,monitor中的Owner會(huì)被賦值為null,計(jì)數(shù)器賦值為0。如下圖所示
3、鎖的種類、升級(jí)和對(duì)比
a)鎖的種類
Java 中鎖的種類大致分為偏向鎖,自旋鎖,輕量級(jí)鎖,重量級(jí)鎖。鎖的使用方式為:先提供偏向鎖,如果不滿足的時(shí)候,升級(jí)為輕量級(jí)鎖,再不滿足,升級(jí)為重量級(jí)鎖。自旋鎖是一個(gè)過渡的鎖狀態(tài),不是一種實(shí)際的鎖類型。鎖只能升級(jí),不能降級(jí)。
b)鎖的升級(jí)
①偏向鎖
如果代碼中基本不可能出現(xiàn)多線程并發(fā)爭(zhēng)搶同一個(gè)鎖的時(shí)候,JVM 編譯代碼,解釋執(zhí)行的時(shí)候,會(huì)自動(dòng)的放棄同步信息,消除 synchronized 的同步代碼結(jié)果,使用鎖標(biāo)記的形式記錄鎖狀態(tài)。具體的實(shí)現(xiàn)方式大概就是:當(dāng)一個(gè)線程訪問同步塊并獲取鎖的時(shí)候,會(huì)在對(duì)象頭和棧幀的鎖記錄中存儲(chǔ)偏向的線程ID,之后線程在進(jìn)入和退出同步塊的時(shí)候不需要使用CAS進(jìn)行加鎖和解鎖,只需要測(cè)試對(duì)象頭中的MarkWord中是否存儲(chǔ)著當(dāng)前線程的偏向鎖;如果測(cè)試成功,就表示線程獲取鎖成功,如果測(cè)試失敗需要檢查對(duì)象頭中的MarkWord的偏向鎖表示是否設(shè)置為1,如果沒有設(shè)置就使用CAS競(jìng)爭(zhēng)鎖,設(shè)置了就以CAS方式將偏向鎖設(shè)置為當(dāng)前線程。在 Monitor 中有變量 ACC_SYNCHRONIZED。當(dāng)變量值使用的時(shí)候,代表偏向鎖鎖定。使用偏向鎖可以避免鎖的爭(zhēng)搶和鎖池狀態(tài)的維護(hù)。提高效率。
②輕量級(jí)鎖
當(dāng)偏向鎖不滿足,也就是有多線程并發(fā)訪問,鎖定同一個(gè)對(duì)象的時(shí)候,先提升為輕量級(jí)鎖。也是使用標(biāo)記 ACC_SYNCHRONIZED 標(biāo)記記錄的。ACC_UNSYNCHRONIZED 標(biāo)記記錄未獲取到鎖信息的線程。就是只有兩個(gè)線程爭(zhēng)搶鎖標(biāo)記的時(shí)候,優(yōu)先使用輕量級(jí)鎖。(自旋鎖)當(dāng)獲取鎖的過程中,未獲取到。為了提高效率,JVM 自動(dòng)執(zhí)行若干次空循環(huán),再次申請(qǐng)鎖,而不是進(jìn)入阻塞狀態(tài)的情況。稱為自旋鎖。自旋鎖提高效率就是避免線程狀態(tài)的變更
③重量級(jí)鎖
在自旋過程中,為了避免無用的自旋(比如獲得鎖的線程被阻塞住了),鎖就會(huì)被升級(jí)為重量級(jí)鎖。在重量級(jí)鎖的狀態(tài)下,其他線程視圖獲取鎖的時(shí)候都會(huì)被阻塞住,只有持有鎖的線程釋放鎖之后才會(huì)喚醒那些阻塞的線程,這些線程就開始競(jìng)爭(zhēng)鎖。
4、關(guān)于synchronized的其他說明
a)關(guān)于同步方法和非同步方法
同步方法只影響 鎖定同一個(gè)鎖對(duì)象的同步方法,不影響非同步方法被其他線程調(diào)用,也不影響其他所資源的同步方法(簡(jiǎn)單理解就是鎖的不是同一個(gè)資源,就不會(huì)影響);
b)synchronized是可重入的
同一個(gè)線程,多次調(diào)用同步代碼,鎖定同一個(gè)對(duì)象,是可重入的;
c)關(guān)于同步的繼承問題
同一個(gè)線程中,子類同步方法覆蓋父類的同步方法,可以指定調(diào)用父類的同步方法(相當(dāng)于鎖的重入)
d)鎖與異常
當(dāng)同步方法出現(xiàn)異常的時(shí)候會(huì)自動(dòng)釋放鎖,不會(huì)影響其他線程的執(zhí)行
e)synchronized鎖的是對(duì)象,而不是引用
同步代碼一旦加鎖之后會(huì)有一個(gè)臨時(shí)鎖引用執(zhí)行鎖對(duì)象,和真實(shí)的引用無直接關(guān)聯(lián),在鎖釋放之前,修改鎖對(duì)象引用不會(huì)影響同步代碼塊的執(zhí)行
f)synchronized中的常量問題
在定義同步代碼塊的時(shí)候,不要使用常量對(duì)象作為鎖對(duì)象
以上就是詳解Java中的鎖Lock和synchronized的詳細(xì)內(nèi)容,更多關(guān)于Java Lock synchronized的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- java中synchronized Lock(本地同步)鎖的8種情況
- Java 多線程Synchronized和Lock的區(qū)別
- Java線程安全解決方案(synchronized,ReentrantLock,Atomic)
- Java synchronized關(guān)鍵字和Lock接口實(shí)現(xiàn)原理
- 深入理解java內(nèi)置鎖(synchronized)和顯式鎖(ReentrantLock)
- Java編程synchronized與lock的區(qū)別【推薦】
- 深入Synchronized和java.util.concurrent.locks.Lock的區(qū)別詳解
- Java中提供synchronized后為什么還要提供Lock
相關(guān)文章
Java并發(fā)編程之LongAdder執(zhí)行情況解析
這篇文章主要為大家介紹了Java并發(fā)編程之LongAdder執(zhí)行情況解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04我從jdk1.8升級(jí)到j(luò)dk11所遇到的坑都有這些
這篇文章主要介紹了從jdk1.8升級(jí)到j(luò)dk11將會(huì)遇到的一些坑,本文給大家分享解決方案對(duì)大家的學(xué)習(xí)或工作具有參考借鑒價(jià)值,對(duì)jdk1.8升級(jí)到j(luò)dk11相關(guān)知識(shí)感興趣的朋友,快來看看吧2021-08-08springboot restTemplate連接池整合方式
這篇文章主要介紹了springboot restTemplate連接池整合方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10Springboot實(shí)現(xiàn)人臉識(shí)別與WebSocket長(zhǎng)連接的實(shí)現(xiàn)代碼
這篇文章主要介紹了Springboot實(shí)現(xiàn)人臉識(shí)別與WebSocket長(zhǎng)連接的實(shí)現(xiàn),本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2023-11-11Java測(cè)試框架Mockito的簡(jiǎn)明教程
這篇文章主要介紹了Java測(cè)試框架Mockito的簡(jiǎn)明教程,Mock 測(cè)試是單元測(cè)試的重要方法之一。本文介紹了基于 Java 語言的 Mock 測(cè)試框架 – Mockito 的使用。,需要的朋友可以參考下2019-06-06mybatis使用foreach查詢不出結(jié)果也不報(bào)錯(cuò)的問題
這篇文章主要介紹了mybatis使用foreach查詢不出結(jié)果也不報(bào)錯(cuò)的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03