<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      【BAT面試題系列】面試官:你了解樂觀鎖和悲觀鎖嗎?

      前言

      樂觀鎖和悲觀鎖問題,是出現(xiàn)頻率比較高的面試題。本文將由淺入深,逐步介紹它們的基本概念、實現(xiàn)方式(含實例)、適用場景,以及可能遇到的面試官追問,希望能夠幫助你打動面試官。

      目錄

      一、基本概念

      二、實現(xiàn)方式(含實例)

            1、CAS(Compare And Swap)

            2、版本號機制

      三、優(yōu)缺點和適用場景

      四、面試官追問:樂觀鎖加鎖嗎?

      五、面試官追問:CAS有哪些缺點?

      六、總結(jié)

      一、基本概念

      樂觀鎖和悲觀鎖是兩種思想,用于解決并發(fā)場景下的數(shù)據(jù)競爭問題。

      • 樂觀鎖:樂觀鎖在操作數(shù)據(jù)時非常樂觀,認(rèn)為別人不會同時修改數(shù)據(jù)。因此樂觀鎖不會上鎖,只是在執(zhí)行更新的時候判斷一下在此期間別人是否修改了數(shù)據(jù):如果別人修改了數(shù)據(jù)則放棄操作,否則執(zhí)行操作。
      • 悲觀鎖:悲觀鎖在操作數(shù)據(jù)時比較悲觀,認(rèn)為別人會同時修改數(shù)據(jù)。因此操作數(shù)據(jù)時直接把數(shù)據(jù)鎖住,直到操作完成后才會釋放鎖;上鎖期間其他人不能修改數(shù)據(jù)。

      二、實現(xiàn)方式(含實例)

      在說明實現(xiàn)方式之前,需要明確:樂觀鎖和悲觀鎖是兩種思想,它們的使用是非常廣泛的,不局限于某種編程語言或數(shù)據(jù)庫。

      悲觀鎖的實現(xiàn)方式是加鎖,加鎖既可以是對代碼塊加鎖(如Java的synchronized關(guān)鍵字),也可以是對數(shù)據(jù)加鎖(如MySQL中的排它鎖)。

      樂觀鎖的實現(xiàn)方式主要有兩種:CAS機制和版本號機制,下面詳細(xì)介紹。

      1、CAS(Compare And Swap)

      CAS操作包括了3個操作數(shù):

      • 需要讀寫的內(nèi)存位置(V)
      • 進(jìn)行比較的預(yù)期值(A)
      • 擬寫入的新值(B)

      CAS操作邏輯如下:如果內(nèi)存位置V的值等于預(yù)期的A值,則將該位置更新為新值B,否則不進(jìn)行任何操作。許多CAS的操作是自旋的:如果操作不成功,會一直重試,直到操作成功為止。

      這里引出一個新的問題,既然CAS包含了Compare和Swap兩個操作,它又如何保證原子性呢?答案是:CAS是由CPU支持的原子操作,其原子性是在硬件層面進(jìn)行保證的。

       

      下面以Java中的自增操作(i++)為例,看一下悲觀鎖和CAS分別是如何保證線程安全的。我們知道,在Java中自增操作不是原子操作,它實際上包含三個獨立的操作:(1)讀取i值;(2)加1;(3)將新值寫回i

      因此,如果并發(fā)執(zhí)行自增操作,可能導(dǎo)致計算結(jié)果的不準(zhǔn)確。在下面的代碼示例中:value1沒有進(jìn)行任何線程安全方面的保護(hù),value2使用了樂觀鎖(CAS),value3使用了悲觀鎖(synchronized)。運行程序,使用1000個線程同時對value1、value2和value3進(jìn)行自增操作,可以發(fā)現(xiàn):value2和value3的值總是等于1000,而value1的值常常小于1000。

      public class Test {
      	
      	//value1:線程不安全
      	private static int value1 = 0;
      	//value2:使用樂觀鎖
      	private static AtomicInteger value2 = new AtomicInteger(0);
      	//value3:使用悲觀鎖
      	private static int value3 = 0;
      	private static synchronized void increaseValue3(){
      		value3++;
      	}
      	
      	public static void main(String[] args) throws Exception {
      		//開啟1000個線程,并執(zhí)行自增操作
      		for(int i = 0; i < 1000; ++i){
      			new Thread(new Runnable() {
      				@Override
      				public void run() {
      					try {
      						Thread.sleep(100);
      					} catch (InterruptedException e) {
      						e.printStackTrace();
      					}
      					value1++;
      					value2.getAndIncrement();
      					increaseValue3();
      				}
      			}).start();
      		}
      		//打印結(jié)果
      		Thread.sleep(1000);
      		System.out.println("線程不安全:" + value1);
      		System.out.println("樂觀鎖(AtomicInteger):" + value2);
      		System.out.println("悲觀鎖(synchronized):" + value3);
      	}
      }

      首先來介紹AtomicInteger。AtomicInteger是java.util.concurrent.atomic包提供的原子類,利用CPU提供的CAS操作來保證原子性;除了AtomicInteger外,還有AtomicBoolean、AtomicLong、AtomicReference等眾多原子類。

      下面看一下AtomicInteger的源碼,了解下它的自增操作getAndIncrement()是如何實現(xiàn)的(源碼以Java7為例,Java8有所不同,但思想類似)。

      public class AtomicInteger extends Number implements java.io.Serializable {
      	//存儲整數(shù)值,volatile保證可視性
      	private volatile int value;
      	//Unsafe用于實現(xiàn)對底層資源的訪問
      	private static final Unsafe unsafe = Unsafe.getUnsafe();
      
      	//valueOffset是value在內(nèi)存中的偏移量
      	private static final long valueOffset;
      	//通過Unsafe獲得valueOffset
          static {
      		try {
      			valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
      		} catch (Exception ex) { throw new Error(ex); }
          }
      
          public final boolean compareAndSet(int expect, int update) {
          	return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
      	}
      
          public final int getAndIncrement() {
              for (;;) {
                  int current = get();
                  int next = current + 1;
                  if (compareAndSet(current, next))
                      return current;
              }
          }
      }

      源碼分析說明如下:

      (1)getAndIncrement()實現(xiàn)的自增操作是自旋CAS操作:在循環(huán)中進(jìn)行compareAndSet,如果執(zhí)行成功則退出,否則一直執(zhí)行。

      (2)其中compareAndSet是CAS操作的核心,它是利用Unsafe對象實現(xiàn)的。

      (3)Unsafe又是何許人也呢?Unsafe是用來幫助Java訪問操作系統(tǒng)底層資源的類(如可以分配內(nèi)存、釋放內(nèi)存),通過Unsafe,Java具有了底層操作能力,可以提升運行效率;強大的底層資源操作能力也帶來了安全隱患(類的名字Unsafe也在提醒我們這一點),因此正常情況下用戶無法使用。AtomicInteger在這里使用了Unsafe提供的CAS功能。

      (4)valueOffset可以理解為value在內(nèi)存中的偏移量,對應(yīng)了CAS三個操作數(shù)(V/A/B)中的V;偏移量的獲得也是通過Unsafe實現(xiàn)的。

      (5)value域的volatile修飾符:Java并發(fā)編程要保證線程安全,需要保證原子性、可視性和有序性;CAS操作可以保證原子性,而volatile可以保證可視性和一定程度的有序性;在AtomicInteger中,volatile和CAS一起保證了線程安全性。關(guān)于volatile作用原理的說明涉及到Java內(nèi)存模型(JMM),這里不詳細(xì)展開。

      說完了AtomicInteger,再說synchronized。synchronized通過對代碼塊加鎖來保證線程安全:在同一時刻,只能有一個線程可以執(zhí)行代碼塊中的代碼。synchronized是一個重量級的操作,不僅是因為加鎖需要消耗額外的資源,還因為線程狀態(tài)的切換會涉及操作系統(tǒng)核心態(tài)和用戶態(tài)的轉(zhuǎn)換;不過隨著JVM對鎖進(jìn)行的一系列優(yōu)化(如自旋鎖、輕量級鎖、鎖粗化等),synchronized的性能表現(xiàn)已經(jīng)越來越好。

      2、版本號機制

      除了CAS,版本號機制也可以用來實現(xiàn)樂觀鎖。版本號機制的基本思路是在數(shù)據(jù)中增加一個字段version,表示該數(shù)據(jù)的版本號,每當(dāng)數(shù)據(jù)被修改,版本號加1。當(dāng)某個線程查詢數(shù)據(jù)時,將該數(shù)據(jù)的版本號一起查出來;當(dāng)該線程更新數(shù)據(jù)時,判斷當(dāng)前版本號與之前讀取的版本號是否一致,如果一致才進(jìn)行操作。

      需要注意的是,這里使用了版本號作為判斷數(shù)據(jù)變化的標(biāo)記,實際上可以根據(jù)實際情況選用其他能夠標(biāo)記數(shù)據(jù)版本的字段,如時間戳等。

       

      下面以“更新玩家金幣數(shù)”為例(數(shù)據(jù)庫為MySQL,其他數(shù)據(jù)庫同理),看看悲觀鎖和版本號機制是如何應(yīng)對并發(fā)問題的。

      考慮這樣一種場景:游戲系統(tǒng)需要更新玩家的金幣數(shù),更新后的金幣數(shù)依賴于當(dāng)前狀態(tài)(如金幣數(shù)、等級等),因此更新前需要先查詢玩家當(dāng)前狀態(tài)。

      下面的實現(xiàn)方式,沒有進(jìn)行任何線程安全方面的保護(hù)。如果有其他線程在query和update之間更新了玩家的信息,會導(dǎo)致玩家金幣數(shù)的不準(zhǔn)確。

      @Transactional
      public void updateCoins(Integer playerId){
      	//根據(jù)player_id查詢玩家信息
      	Player player = query("select coins, level from player where player_id = {0}", playerId);
      	//根據(jù)玩家當(dāng)前信息及其他信息,計算新的金幣數(shù)
      	Long newCoins = ……;
      	//更新金幣數(shù)
      	update("update player set coins = {0} where player_id = {1}", newCoins, playerId);
      }

      為了避免這個問題,悲觀鎖通過加鎖解決這個問題,代碼如下所示。在查詢玩家信息時,使用select …… for update進(jìn)行查詢;該查詢語句會為該玩家數(shù)據(jù)加上排它鎖,直到事務(wù)提交或回滾時才會釋放排它鎖;在此期間,如果其他線程試圖更新該玩家信息或者執(zhí)行select for update,會被阻塞。

      @Transactional
      public void updateCoins(Integer playerId){
      	//根據(jù)player_id查詢玩家信息(加排它鎖)
      	Player player = queryForUpdate("select coins, level from player where player_id = {0} for update", playerId);
      	//根據(jù)玩家當(dāng)前信息及其他信息,計算新的金幣數(shù)
      	Long newCoins = ……;
      	//更新金幣數(shù)
      	update("update player set coins = {0} where player_id = {1}", newCoins, playerId);
      }

      版本號機制則是另一種思路,它為玩家信息增加一個字段:version。在初次查詢玩家信息時,同時查詢出version信息;在執(zhí)行update操作時,校驗version是否發(fā)生了變化,如果version變化,則不進(jìn)行更新。

      @Transactional
      public void updateCoins(Integer playerId){
      	//根據(jù)player_id查詢玩家信息,包含version信息
      	Player player = query("select coins, level, version from player where player_id = {0}", playerId);
      	//根據(jù)玩家當(dāng)前信息及其他信息,計算新的金幣數(shù)
      	Long newCoins = ……;
      	//更新金幣數(shù),條件中增加對version的校驗
      	update("update player set coins = {0}, version = version + 1 where player_id = {1} and version = {2}", newCoins, playerId, player.version);
      }

      三、優(yōu)缺點和適用場景

      樂觀鎖和悲觀鎖并沒有優(yōu)劣之分,它們有各自適合的場景;下面從兩個方面進(jìn)行說明。

      1、功能限制

      與悲觀鎖相比,樂觀鎖適用的場景受到了更多的限制,無論是CAS還是版本號機制。

      例如,CAS只能保證單個變量操作的原子性,當(dāng)涉及到多個變量時,CAS是無能為力的,而synchronized則可以通過對整個代碼塊加鎖來處理。再比如版本號機制,如果query的時候是針對表1,而update的時候是針對表2,也很難通過簡單的版本號來實現(xiàn)樂觀鎖。

      2、競爭激烈程度

      如果悲觀鎖和樂觀鎖都可以使用,那么選擇就要考慮競爭的激烈程度:

      • 當(dāng)競爭不激烈 (出現(xiàn)并發(fā)沖突的概率小)時,樂觀鎖更有優(yōu)勢,因為悲觀鎖會鎖住代碼塊或數(shù)據(jù),其他線程無法同時訪問,影響并發(fā),而且加鎖和釋放鎖都需要消耗額外的資源。
      • 當(dāng)競爭激烈(出現(xiàn)并發(fā)沖突的概率大)時,悲觀鎖更有優(yōu)勢,因為樂觀鎖在執(zhí)行更新時頻繁失敗,需要不斷重試,浪費CPU資源。

      四、面試官追問:樂觀鎖加鎖嗎?

      筆者在面試時,曾遇到面試官如此追問。下面是我對這個問題的理解:

      (1)樂觀鎖本身是不加鎖的,只是在更新時判斷一下數(shù)據(jù)是否被其他線程更新了;AtomicInteger便是一個例子。

      (2)有時樂觀鎖可能與加鎖操作合作,例如,在前述updateCoins()的例子中,MySQL在執(zhí)行update時會加排它鎖。但這只是樂觀鎖與加鎖操作合作的例子,不能改變“樂觀鎖本身不加鎖”這一事實。

      五、面試官追問:CAS有哪些缺點?

      面試到這里,面試官可能已經(jīng)中意你了。不過面試官準(zhǔn)備對你發(fā)起最后的進(jìn)攻:你知道CAS這種實現(xiàn)方式有什么缺點嗎?

      下面是CAS一些不那么完美的地方:

      1、ABA問題

      假設(shè)有兩個線程——線程1和線程2,兩個線程按照順序進(jìn)行以下操作:

      (1)線程1讀取內(nèi)存中數(shù)據(jù)為A;

      (2)線程2將該數(shù)據(jù)修改為B;

      (3)線程2將該數(shù)據(jù)修改為A;

      (4)線程1對數(shù)據(jù)進(jìn)行CAS操作

      在第(4)步中,由于內(nèi)存中數(shù)據(jù)仍然為A,因此CAS操作成功,但實際上該數(shù)據(jù)已經(jīng)被線程2修改過了。這就是ABA問題。

      在AtomicInteger的例子中,ABA似乎沒有什么危害。但是在某些場景下,ABA卻會帶來隱患,例如棧頂問題:一個棧的棧頂經(jīng)過兩次(或多次)變化又恢復(fù)了原值,但是棧可能已發(fā)生了變化。

      對于ABA問題,比較有效的方案是引入版本號,內(nèi)存中的值每發(fā)生一次變化,版本號都+1;在進(jìn)行CAS操作時,不僅比較內(nèi)存中的值,也會比較版本號,只有當(dāng)二者都沒有變化時,CAS才能執(zhí)行成功。Java中的AtomicStampedReference類便是使用版本號來解決ABA問題的。

      2、高競爭下的開銷問題

      在并發(fā)沖突概率大的高競爭環(huán)境下,如果CAS一直失敗,會一直重試,CPU開銷較大。針對這個問題的一個思路是引入退出機制,如重試次數(shù)超過一定閾值后失敗退出。當(dāng)然,更重要的是避免在高競爭環(huán)境下使用樂觀鎖。

      3、功能限制

      CAS的功能是比較受限的,例如CAS只能保證單個變量(或者說單個內(nèi)存值)操作的原子性,這意味著:(1)原子性不一定能保證線程安全,例如在Java中需要與volatile配合來保證線程安全;(2)當(dāng)涉及到多個變量(內(nèi)存值)時,CAS也無能為力。

      除此之外,CAS的實現(xiàn)需要硬件層面處理器的支持,在Java中普通用戶無法直接使用,只能借助atomic包下的原子類使用,靈活性受到限制。

      六、總結(jié)

      本文介紹了樂觀鎖和悲觀鎖的基本概念、實現(xiàn)方式(含實例)、適用場景,以及可能遇到的面試官追問,希望能夠?qū)δ忝嬖囉袔椭W詈螅4蠹叶寄玫叫膬x的offer!

      參考文獻(xiàn)

      http://www.rzrgm.cn/qjjazry/p/6581568.html

      https://segmentfault.com/a/1190000016611415

      http://www.rzrgm.cn/pkufork/p/java_unsafe.html

      https://stackoverflow.com/questions/19660737/aba-in-lock-free-algorithms

      https://www.zhihu.com/question/23281499

       

      創(chuàng)作不易,如果文章對你有幫助,就點個贊、評個論唄~

      創(chuàng)作不易,如果文章對你有幫助,就點個贊、評個論唄~

      創(chuàng)作不易,如果文章對你有幫助,就點個贊、評個論唄~

       

      posted @ 2019-04-29 08:30  編程迷思  閱讀(67000)  評論(33)    收藏  舉報
      主站蜘蛛池模板: 中文字幕精品亚洲无线码二区| 成人av久久一区二区三区| 欧美成人无码a区视频在线观看| 精品国产一区二区三区2021| 子洲县| 中文字幕亚洲一区二区三区 | 女同精品女同系列在线观看| 秋霞人妻无码中文字幕| 色窝窝免费播放视频在线| 日本一区二区三区在线看 | 久久夜色精品久久噜噜亚| 国产亚洲精品第一综合| 伊人久久大香线蕉av五月天| 亚洲综合国产精品第一页| 久久人妻无码一区二区| 依依成人精品视频在线观看| 男人一天堂精品国产乱码| 人成午夜大片免费视频77777 | 欧美xxxxx高潮喷水| 女人张开腿无遮无挡视频| 精品无码三级在线观看视频| 四虎精品视频永久免费| 久章草在线精品视频免费观看| 91孕妇精品一区二区三区| 视频一区二区三区自拍偷拍| 日本成熟少妇喷浆视频| 在线视频中文字幕二区| 成人免费无码大片a毛片| 最新国产精品拍自在线观看| 亚洲欧美牲交| 国产精品视频第一第二区| 二区中文字幕在线观看| 色午夜一av男人的天堂| 亚洲综合精品香蕉久久网| 亚洲乱理伦片在线观看中字| 老师破女学生处特级毛ooo片| 无码伊人久久大杳蕉中文无码| 99久久亚洲综合网精品| 日韩不卡无码精品一区高清视频 | √天堂中文在线最新版| 公喝错春药让我高潮|