在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

多線程常見鎖策略+CAS介紹

Android編程精選 ? 來源:CSDN ? 2023-03-14 16:30 ? 次閱讀

一、樂觀鎖 & 悲觀鎖

1.1 樂觀鎖的定義

樂觀鎖,顧名思義,他比較樂觀,他認為一般情況下不會出現沖突,所以只會在更新數據的時候才會對沖突進行檢測。如果沒有發生沖突直接進行修改,如果發生了沖突則不進行任何修改,然后把結果返回給用戶,讓用戶自行處理。

1.1.1樂觀鎖的實現-CAS

樂觀鎖的實現并不是給數據加鎖 ,而是通過CAS(Compare And Swap)比較并替換,來實現樂觀鎖的效果。

CAS比較并替換的流程是這樣子的:CAS中包含了三個操作,單位:V(內存值)、A(預期的舊址)、B(新值),比較V值和A值是否相等,,如果相等的話則將V的值更換成B,否則就提示用戶修改失敗,從而實現了CAS機制。

這只是定義的流程,但是在實際執行過程中,并不會當V值和A值不相等時,就立即把結果返回給用戶,而是將A(預期的舊值)改為內存中最新的值,然后再進行比較,直到V值也A值相等,修改內存中的值為B結束。

可能你還是覺得有些晦澀,那我們舉個栗子:

41ee6676-bf62-11ed-bfe3-dac502259ad0.png

看完這個圖相信你一定能理解了CAS的執行流程了。

1.1.2 CAS的應用

CAS的底層實現是靠Unsafe類實現的,Unsafe是CAS的核心類,由于Java方法無法直接訪問底層系統,需要通過本地(Native)方法來訪問,Unsafe相當于一個后門,基于該類可以直接操作特定的內存數據。Unsafe類存在sun.misc包中,其內部方法操作可以像C的指針一樣直接操作內存,因為Java中的CAS操作的執行依賴于Unsafe類的方法。

注意Unsafe類的所有方法都是native修飾的,也就是說Unsafe類中的方法都直接調用操作系統底層資源執行相應的任務。因此不推薦使用Unsafe類,如果用不好會對底層資源造成影響。

為什么Atomic修飾的包裝類,能夠保證原子性,依靠的就是底層的unsafe類,我們來看看AtomicInteger的源碼:

41f6b146-bf62-11ed-bfe3-dac502259ad0.png

在getAndIncrement方法中還調用了unsafe的方法,因此這也就是為什么它能夠保證原子性的原因。

因此我們可以利用Atomic+包裝類實現線程安全的問題。

importjava.util.concurrent.atomic.AtomicInteger;

/**
*使用AtomicInteger保證線程安全問題
*/
publicclassAtomicIntegerDemo{
staticclassCounter{
privatestaticAtomicIntegernum=newAtomicInteger(0);
privateintMAX_COUNT=100000;
publicCounter(intMAX_COUNT){
this.MAX_COUNT=MAX_COUNT;
}
//++方法
publicvoidincrement(){
for(inti=0;i{
counter.increment();
});
Threadthread2=newThread(()->{
counter.decrement();
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("最終結果:"+counter.getNum());
}
}
4207dbe2-bf62-11ed-bfe3-dac502259ad0.png

1.1.3 CAS存在的問題

循環時間長,開銷大

只能保證一個共享變量的原子性操作(可以通過循環CAS的方式實現)

存在ABA問題

1.1.4 ABA問題

什么時ABA問題呢?

比如說兩個線程t1和t2,t1的執行時間為10s,t2的執行時間為2s,剛開始都從主內存中獲取到A值,t2先開始執行,他執行的比較快,于是他將A的值先改為B,再改為A,這時t1執行,判斷內存中的值為A,與自己預期的值一樣,以為這個值沒有修改過,于是將內存中的值修改為B,但是實際上中間可能已經經歷了許多:A->B->A。

所以ABA問題就是,在我們進行CAS中的比較時,預期的值與內存中的值一樣,并不能說明這個值沒有被改過,而是可能已經被修改了,但是又被改回了預期的值。

importjava.util.concurrent.atomic.AtomicInteger;

/**
*ABA問題演示
*/
publicclassABADemo1{
privatestaticAtomicIntegermoney=newAtomicInteger(100);
publicstaticvoidmain(String[]args)throwsInterruptedException{
//第一次點轉賬按鈕(-50)
Threadt1=newThread(()->{
intold_money=money.get();//先得到余額
try{//執行花費2s
Thread.sleep(2000);
}catch(InterruptedExceptione){
e.printStackTrace();
}
money.compareAndSet(old_money,old_money-50);
});
t1.start();

//第二次點擊轉賬按鈕(-50)不小心點擊的,因為第一次點擊之后沒反應,所以不小心又點了一次
Threadt2=newThread(()->{
intold_money=money.get();//先得到余額
money.compareAndSet(old_money,old_money-50);
});
t2.start();

//給賬戶加50
Threadt3=newThread(()->{
//執行花費1s
try{
Thread.sleep(1000);
}catch(InterruptedExceptione){
e.printStackTrace();
}
intold_money=money.get();
money.compareAndSet(old_money,old_money+50);

});
t3.start();

t1.join();
t2.join();
t3.join();
System.out.println("最終的錢數:"+money.get());
}
}
42189b62-bf62-11ed-bfe3-dac502259ad0.png

這個例子演示了ABA問題,A有100元,A向B轉錢,第一次轉了50元,但是點完轉賬按鈕沒有反應,于是又點擊了一次。第一次轉賬成功后A還剩50元,而這時C給A轉了50元,A的余額變為100元,第二次的CAS判斷(100,100,50),A的余額與預期的值一樣,于是將A的余額修改為50元。

1.1.5 ABA問題的解決方案

由于CAS是只管頭和尾是否相等,若相等,就認為這個過程沒問題,因此我們就引出了AtomicStampedReference,時間戳原子引用,在這里應用于版本號的更新。也就是我們新增了一種機制,在每次更新的時候,需要比較當前值和期望值以及當前版本號和期望版本號,若值或版本號有一個不相同,這個過程都是有問題的。

我們來看上面的例子怎么用AtomicStampedReference解決呢?

importjava.util.concurrent.atomic.AtomicInteger;
importjava.util.concurrent.atomic.AtomicStampedReference;

/**
*ABA問題解決添加版本號
*/
publicclassABADemo2{
privatestaticAtomicStampedReferencemoney=
newAtomicStampedReference<>(100,0);
publicstaticvoidmain(String[]args)throwsInterruptedException{
//第一次點轉賬按鈕(-50)
Threadt1=newThread(()->{
intold_money=money.getReference();//先得到余額100
intoldStamp=money.getStamp();//得到舊的版本號
try{//執行花費2s
Thread.sleep(2000);
}catch(InterruptedExceptione){
e.printStackTrace();
}
booleanresult=money.compareAndSet(old_money,old_money-50,oldStamp,oldStamp+1);
System.out.println(Thread.currentThread().getName()+"轉賬:"+result);
},"線程1");
t1.start();

//第二次點擊轉賬按鈕(-50)不小心點擊的,因為第一次點擊之后沒反應,所以不小心又點了一次
Threadt2=newThread(()->{
intold_money=money.getReference();//先得到余額100
intoldStamp=money.getStamp();//得到舊的版本號
booleanresult=money.compareAndSet(old_money,old_money-50,oldStamp,oldStamp+1);
System.out.println(Thread.currentThread().getName()+"轉賬:"+result);
},"線程2");
t2.start();

//給賬戶+50
Threadt3=newThread(()->{
//執行花費1s
try{
Thread.sleep(1000);
}catch(InterruptedExceptione){
e.printStackTrace();
}
intold_money=money.getReference();//先得到余額100
intoldStamp=money.getStamp();//得到舊的版本號
booleanresult=money.compareAndSet(old_money,old_money+50,oldStamp,oldStamp+1);
System.out.println(Thread.currentThread().getName()+"發工資:"+result);

},"線程3");
t3.start();

t1.join();
t2.join();
t3.join();
System.out.println("最終的錢數:"+money.getReference());
}
}
422cd712-bf62-11ed-bfe3-dac502259ad0.png

AtommicStampedReference解決了ABA問題,在每次更新值之前,比較值和版本號。

1.2 悲觀鎖

什么是悲觀鎖?

悲觀鎖就是比較悲觀,總是假設最壞的情況,每次去拿數據的時候都會認為別人會修改,所以在每次拿數據的時候都會上鎖,這樣別人想拿數據就會阻塞直到它拿到鎖。

比如我們之前提到的synchronized和Lock都是悲觀鎖。

二、公平鎖和非公平鎖

公平鎖: 按照線程來的先后順序獲取鎖,當一個線程釋放鎖之后,那么就喚醒阻塞隊列中第一個線程獲取鎖。

4235bb02-bf62-11ed-bfe3-dac502259ad0.png

非公平鎖: 不是按照線程來的先后順序喚醒鎖,而是當有一個線程釋放鎖之后,喚醒阻塞隊列中的所有線程,隨機獲取鎖。

424aa724-bf62-11ed-bfe3-dac502259ad0.png

之前在講synchronized和Lock這兩個鎖解決線程安全問題線程安全問題的解決的時候,我們提過:

synchronized的鎖只能是非公平鎖;

Lock的鎖默認情況下是非公平鎖,而擋在構造 函數中傳入參數時,則是公平鎖;

公平鎖:Lock lock=new ReentrantLock(true);

非公平鎖:Lock lock=new ReentrantLock();

由于公平鎖只能按照線程來的線程順序獲取鎖,因此性能較低,推薦使用非公平鎖。

三、讀寫鎖

3.1 讀寫鎖

讀寫鎖顧名思義是一把鎖分為兩部分:讀鎖和寫鎖。

讀寫鎖的規則是:允許多個線程獲取讀鎖,而寫鎖是互斥鎖,不允許多個線程同時獲得,并且讀操作和寫操作也是 互斥的,總的來說就是讀讀不互斥,讀寫互斥,寫寫互斥。

為什么要這樣設置呢?

讓整個讀寫的操作到設置為互斥不是更方便嗎?

其實只要涉及到“互斥”,就會產生線程掛起等待,一旦掛起等待,,再次被喚醒就不知道什么時候了,因此盡可能的減少“互斥"的機會,就是提高效率的重要途徑。

Java標準庫提供了ReentrantReadWriteLock類實現了讀寫鎖。

ReentrantReadWriteLock.ReadLock類表示一個讀鎖,提供了lock和unlock進行加鎖和解鎖。

ReentrantReadWriteLock.WriteLock類表示一個寫鎖,提供了lock和unlock進行加鎖和解鎖。

下面我們來看下讀寫鎖的使用演示~

importjava.time.LocalDateTime;
importjava.util.concurrent.LinkedBlockingDeque;
importjava.util.concurrent.ThreadPoolExecutor;
importjava.util.concurrent.TimeUnit;
importjava.util.concurrent.locks.ReentrantReadWriteLock;

/**
*演示讀寫鎖的使用
*/
publicclassReadWriteLockDemo1{
publicstaticvoidmain(String[]args){
//創建讀寫鎖
finalReentrantReadWriteLockreentrantReadWriteLock=newReentrantReadWriteLock();
//創建讀鎖
finalReentrantReadWriteLock.ReadLockreadLock=reentrantReadWriteLock.readLock();
//創建寫鎖
finalReentrantReadWriteLock.WriteLockwriteLock=reentrantReadWriteLock.writeLock();
//線程池
ThreadPoolExecutorexecutor=newThreadPoolExecutor(5,5,0,TimeUnit.SECONDS,newLinkedBlockingDeque<>(100));
//啟動線程執行任務【讀操作1】
executor.submit(()->{
//加鎖操作
readLock.lock();
try{
//執行業務邏輯
System.out.println("執行讀鎖1:"+LocalDateTime.now());
TimeUnit.SECONDS.sleep(1);
}catch(InterruptedExceptione){
e.printStackTrace();
}finally{
readLock.unlock();
}
});

//啟動線程執行任務【讀操作2】
executor.submit(()->{
//加鎖操作
readLock.lock();
try{
//執行業務邏輯
System.out.println("執行讀鎖2:"+LocalDateTime.now());
TimeUnit.SECONDS.sleep(1);
}catch(InterruptedExceptione){
e.printStackTrace();
}finally{
//釋放鎖
readLock.unlock();
}
});

//啟動線程執行【寫操作1】
executor.submit(()->{
//加鎖
writeLock.lock();
try{
System.out.println("執行寫鎖1:"+LocalDateTime.now());
TimeUnit.SECONDS.sleep(1);
}catch(InterruptedExceptione){
e.printStackTrace();
}finally{
writeLock.unlock();
}
});

//啟動線程執行【寫操作2】
executor.submit(()->{
//加鎖
writeLock.lock();
try{
System.out.println("執行寫鎖2:"+LocalDateTime.now());
TimeUnit.SECONDS.sleep(1);
}catch(InterruptedExceptione){
e.printStackTrace();
}finally{
writeLock.unlock();
}
});
}
}
426afbbe-bf62-11ed-bfe3-dac502259ad0.png

根據運行結果我們看到,讀鎖操作是一起執行的,而寫鎖操作是互斥執行的。

3.2 獨占鎖

獨占鎖就是指任何時候只能有一個線程能執行資源操作,是互斥的。

比如寫鎖,就是一個獨占鎖,任何時候只能有一個線程執行寫操作,synchronized、Lock都是獨占鎖。

3.3 共享鎖

共享鎖是指可以同時被多個線程獲取,但是只能被一個線程修改。讀寫鎖就是一個典型的共享鎖,它允許多個線程進行讀操作 ,但是只允許一個線程進行寫操作。

四、可重入鎖 & 自旋鎖

4.1 可重入鎖

可重入鎖指的是該線程獲取了該鎖之后,可以無限次的進入該鎖。

因為在對象頭存儲了擁有當前鎖的id,進入鎖之前驗證對象頭的id是否與當前線程id一致,若一致就可進入,因此實現可重入鎖 。

4.2 自旋鎖

自旋鎖是指嘗試獲取鎖的線程不會立即阻塞,而是采取循環的方式嘗試獲取鎖,這樣的好處是減少線程上下文切換的消耗。線程上下文切換就是從用戶態—>內核態。

synchronized就是一種自適應自旋鎖(自旋的次數不固定),hotSpot虛擬機的自旋機制是這一次的自旋次數由上一次自旋獲取鎖的次數來決定,如果上次自旋了很多次才獲取到鎖,那么這次自旋的次數就會降低,因為虛擬機認為這一次大概率還是要自旋很多次才能獲取到鎖,比較浪費系統資源。




審核編輯:劉清

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 虛擬機
    +關注

    關注

    1

    文章

    919

    瀏覽量

    28279
  • CAS
    CAS
    +關注

    關注

    0

    文章

    35

    瀏覽量

    15213
  • ABAT
    +關注

    關注

    0

    文章

    2

    瀏覽量

    6286

原文標題:一篇文章搞定,多線程常見鎖策略+CAS

文章出處:【微信號:AndroidPush,微信公眾號:Android編程精選】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    Java多線程的用法

    本文將介紹一下Java多線程的用法。 基礎介紹 什么是多線程 指的是在一個進程中同時運行多個線程,每個
    的頭像 發表于 09-30 17:07 ?965次閱讀

    C++面向對象多線程編程 (pdf電子版)

    C++面向對象多線程編程共分13章,全面講解構建多線程架構與增量多線程編程技術。第1章介紹
    發表于 09-25 09:39 ?0次下載

    QNX環境下多線程編程

    介紹了QNX 實時操作系統和多線程編程技術,包括線程間同步的方法、多線程程序的分析步驟、線程基本程序結構以及實用編譯方法。QNX 是由加拿大
    發表于 08-12 17:37 ?30次下載

    多線程技術在串口通信中的應用

            首先介紹多線程技術的基本原理,然后討論了多線程技術在串口通信中的應用,并給出了實現的方法和步驟。關鍵詞:
    發表于 09-04 09:10 ?18次下載

    多線程細節問題學習筆記

    這一次我們要說下關于final在多線程的作用,原子性的使用,死鎖以及Java中的應對方案,線程的局部變量 和 讀寫介紹 。關于final變量在
    發表于 11-28 15:34 ?1146次閱讀
    <b class='flag-5'>多線程</b>細節問題學習筆記

    多線程好還是單線程好?單線程多線程的區別 優缺點分析

    摘要:如今單線程多線程已經得到普遍運用,那么到底多線程好還是單線程好呢?單線程多線程的區別又
    發表于 12-08 09:33 ?8.1w次閱讀

    mfc多線程編程實例及代碼,mfc多線程間通信介紹

    摘要:本文主要以MFC多線程為中心,分別對MFC多線程的實例、MFC多線程之間的通信展開的一系列研究,下面我們來看看原文。
    發表于 12-08 15:23 ?1.8w次閱讀
    mfc<b class='flag-5'>多線程</b>編程實例及代碼,mfc<b class='flag-5'>多線程</b>間通信<b class='flag-5'>介紹</b>

    什么是多線程編程?多線程編程基礎知識

    摘要:多線程編程是現代軟件技術中很重要的一個環節。要弄懂多線程,這就要牽涉到多進程。本文主要以多線程編程以及多線程編程相關知識而做出的一些結論。
    發表于 12-08 16:30 ?1.3w次閱讀

    java學習——java面試【事務、、多線程】資料整理

    本文檔內容介紹了基于java學習java面試【事務、多線程】資料整理,供參考
    發表于 03-13 13:53 ?0次下載

    多線程編程指南的PDF電子書免費下載

    多線程編程指南》介紹了 SolarisTM 操作系統 (Solaris Operating System, Solaris OS)中 POSIX?線程和 Solaris 線程
    發表于 06-11 08:00 ?4次下載
    <b class='flag-5'>多線程</b>編程指南的PDF電子書免費下載

    CAS如何實現各種無的數據結構

    ,可用于在多線程編程中實現不被打斷的數據交換操作,從而避免多線程同時改寫某?數據時由于執行順序不確定性以及中斷的不可預知性產?的數據不一致問題 有了CAS,我們就可以用它來實現各種無
    的頭像 發表于 11-13 15:38 ?834次閱讀
    無<b class='flag-5'>鎖</b><b class='flag-5'>CAS</b>如何實現各種無<b class='flag-5'>鎖</b>的數據結構

    多線程同步的幾種方法

    多線程同步是指在多個線程并發執行的情況下,為了保證線程執行的正確性和一致性,需要采用特定的方法來協調線程之間的執行順序和共享資源的訪問。下面將介紹
    的頭像 發表于 11-17 14:16 ?1197次閱讀

    多線程如何保證數據的同步

    。本文將詳細介紹多線程數據同步的概念、問題、以及常見的解決方案。 一、多線程數據同步概念 在多線程編程中,數據同步指的是通過某種機制來確保多
    的頭像 發表于 11-17 14:22 ?1263次閱讀

    mfc多線程編程實例

    (圖形用戶界面)應用程序的開發。在這篇文章中,我們將重點介紹MFC中的多線程編程。 多線程編程在軟件開發中非常重要,它可以實現程序的并發執行,提高程序的效率和響應速度。MFC提供了豐富的多線程
    的頭像 發表于 12-01 14:29 ?1523次閱讀

    java實現多線程的幾種方式

    了多種實現多線程的方式,本文將詳細介紹以下幾種方式: 1.繼承Thread類 2.實現Runnable接口 3.Callable和Future 4.線程池 5.Java 8中
    的頭像 發表于 03-14 16:55 ?741次閱讀
    主站蜘蛛池模板: 黄色aa毛片| 国产69精品久久久久9999| 停停色| 五月婷婷六月丁香综合| 新版bt天堂资源在线| 特黄特色网站| 日本高清色视频在线观看免费| 日韩精品免费一区二区三区| 人人搞人人爽| 精品伊人久久香线蕉| 成人区精品一区二区毛片不卡| 99国产精品农村一级毛片| 夜夜操综合| 久久riav国产精品| 黄色高清视频网站| 免费视频性| 性欧美护士18xxxxhd| 色综合久久网| 毛片在线看免费版| 丁香色婷婷| 天堂最新在线资源| 欧美性黄色| 免费黄视频在线观看| 亚洲精品亚洲人成人网| 日本午夜片| 国产激情电影综合在线看| 天天黄色| 51精品视频免费国产专区| 日本一区二区三区欧美在线观看 | 手机看片91| 欧美性极品hd高清视频| 国产精品久久久久久久久免费观看 | 天天干天天上| 欧美一级淫片免费播放口| 女主播扒开内衣让粉丝看个够| 婷婷色在线| 黄色一级片在线观看| 天天综合视频网| 黄色大片在线免费观看| 亚洲五月综合网色九月色| 欧美精品一区视频|