最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

淺談Java鎖機(jī)制

 更新時(shí)間:2021年09月30日 16:10:13   作者:隨身電源  
在多線程環(huán)境下,程序往往會(huì)出現(xiàn)一些線程安全問(wèn)題,為此,Java提供了一些線程的同步機(jī)制來(lái)解決安全問(wèn)題,比如:synchronized鎖和Lock鎖都能解決線程安全問(wèn)題。下面小編就來(lái)詳細(xì)介紹該知識(shí)點(diǎn),需要的朋友可以參考一下

1、悲觀鎖和樂(lè)觀鎖

我們可以將鎖大體分為兩類:

  • 悲觀鎖
  • 樂(lè)觀鎖

顧名思義,悲觀鎖總是假設(shè)最壞的情況,每次獲取數(shù)據(jù)的時(shí)候都認(rèn)為別的線程會(huì)修改,所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖,這樣其它線程想要修改這個(gè)數(shù)據(jù)的時(shí)候都會(huì)被阻塞直到獲取鎖。比如MySQL數(shù)據(jù)庫(kù)中的表鎖、行鎖、讀鎖、寫(xiě)鎖等,Java中的synchronizedReentrantLock等。

而樂(lè)觀鎖總是假設(shè)最好的情況,每次獲取數(shù)據(jù)的時(shí)候都認(rèn)為別的線程不會(huì)修改,所以并不會(huì)上鎖,但是在修改數(shù)據(jù)的時(shí)候需要判斷一下在此期間有沒(méi)有別的線程修改過(guò)數(shù)據(jù),如果沒(méi)有修改過(guò)則正常修改,如果修改過(guò)則這次修改就是失敗的。常見(jiàn)的樂(lè)觀鎖有版本號(hào)控制、CAS算法等。

2、悲觀鎖應(yīng)用

案例如下:

public class LockDemo {

    static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        List<Thread> threadList = new ArrayList<>();
        for (int i = 0; i < 50; i++) {
            Thread thread = new Thread(() -> {
                for (int j = 0; j < 1000; ++j) {
                    count++;
                }
            });
            thread.start();
            threadList.add(thread);
        }
        // 等待所有線程執(zhí)行完畢
        for (Thread thread : threadList) {
            thread.join();
        }
        System.out.println(count);
    }
}

在該程序中一共開(kāi)啟了50個(gè)線程,并在線程中對(duì)共享變量count進(jìn)行++操作,所以如果不發(fā)生線程安全問(wèn)題,最終的結(jié)果應(yīng)該是50000,但該程序中一定存在線程安全問(wèn)題,運(yùn)行結(jié)果為:

48634

若想解決線程安全問(wèn)題,可以使用synchronized關(guān)鍵字:

public class LockDemo {

    static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        List<Thread> threadList = new ArrayList<>();
        for (int i = 0; i < 50; i++) {
            Thread thread = new Thread(() -> {
                // 使用synchronized關(guān)鍵字解決線程安全問(wèn)題
                synchronized (LockDemo.class) {
                    for (int j = 0; j < 1000; ++j) {
                        count++;
                    }
                }
            });
            thread.start();
            threadList.add(thread);
        }
        for (Thread thread : threadList) {
            thread.join();
        }
        System.out.println(count);
    }
}

將修改count變量的操作使用synchronized關(guān)鍵字包裹起來(lái),這樣當(dāng)某個(gè)線程在進(jìn)行++操作時(shí),別的線程是無(wú)法同時(shí)進(jìn)行++的,只能等待前一個(gè)線程執(zhí)行完1000次后才能繼續(xù)執(zhí)行,這樣便能保證最終的結(jié)果為50000。

使用ReentrantLock也能夠解決線程安全問(wèn)題:

public class LockDemo {

    static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        List<Thread> threadList = new ArrayList<>();
        Lock lock = new ReentrantLock();
        for (int i = 0; i < 50; i++) {
            Thread thread = new Thread(() -> {
                // 使用ReentrantLock關(guān)鍵字解決線程安全問(wèn)題
                lock.lock();
                try {
                    for (int j = 0; j < 1000; ++j) {
                        count++;
                    }
                } finally {
                    lock.unlock();
                }
            });
            thread.start();
            threadList.add(thread);
        }
        for (Thread thread : threadList) {
            thread.join();
        }
        System.out.println(count);
    }
}

這兩種鎖機(jī)制都是悲觀鎖的具體實(shí)現(xiàn),不管其它線程是否會(huì)同時(shí)修改,它都直接上鎖,保證了原子操作。

3、樂(lè)觀鎖應(yīng)用

由于線程的調(diào)度是極其耗費(fèi)操作系統(tǒng)資源的,所以,我們應(yīng)該盡量避免線程在不斷阻塞和喚醒中切換,由此產(chǎn)生了樂(lè)觀鎖。

在數(shù)據(jù)庫(kù)表中,我們往往會(huì)設(shè)置一個(gè)version字段,這就是樂(lè)觀鎖的體現(xiàn),假設(shè)某個(gè)數(shù)據(jù)表的數(shù)據(jù)內(nèi)容如下:

+----+------+----------+ ------- +
| id | name | password | version |
+----+------+----------+ ------- +
|  1 | zs   | 123456   |    1    |
+----+------+----------+ ------- +

它是如何避免線程安全問(wèn)題的呢?

假設(shè)此時(shí)有兩個(gè)線程A、B想要修改這條數(shù)據(jù),它們會(huì)執(zhí)行如下的sql語(yǔ)句:

select version from e_user where name = 'zs';

update e_user set password = 'admin',version = version + 1 where name = 'zs' and version = 1;

首先兩個(gè)線程均查詢出zs用戶的版本號(hào)為1,然后線程A先執(zhí)行了更新操作,此時(shí)將用戶的密碼修改為了admin,并將版本號(hào)加1,接著線程B執(zhí)行更新操作,此時(shí)版本號(hào)已經(jīng)為2了,所以更新肯定是失敗的,由此,線程B就失敗了,它只能重新去獲取版本號(hào)再進(jìn)行更新,這就是樂(lè)觀鎖,我們并沒(méi)有對(duì)程序和數(shù)據(jù)庫(kù)進(jìn)行任何的加鎖操作,但它仍然能夠保證線程安全。

4、CAS

仍然以最開(kāi)始做加法的程序?yàn)槔?,在Java中,我們還可以采用一種特殊的方式來(lái)實(shí)現(xiàn)它:

public class LockDemo {

    static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        List<Thread> threadList = new ArrayList<>();
        for (int i = 0; i < 50; i++) {
            Thread thread = new Thread(() -> {
                for (int j = 0; j < 1000; ++j) {
                    // 使用AtomicInteger解決線程安全問(wèn)題
                    count.incrementAndGet();
                }
            });
            thread.start();
            threadList.add(thread);
        }
        for (Thread thread : threadList) {
            thread.join();
        }
        System.out.println(count);
    }
}

為何使用AtomicInteger類就能夠解決線程安全問(wèn)題呢?

我們來(lái)查看一下源碼:

public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

當(dāng)count調(diào)用incrementAndGet()方法時(shí),實(shí)際上調(diào)用的是UnSafe類的getAndAddInt()方法:

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));

    return var5;
}

getAndAddInt()方法中有一個(gè)循環(huán),關(guān)鍵的代碼就在這里,我們假設(shè)線程A此時(shí)進(jìn)入了該方法,此時(shí)var1即為AtomicInteger對(duì)象(初始值為0),var2的值為12(這是一個(gè)內(nèi)存偏移量,我們可以不用關(guān)心),var4的值為1(準(zhǔn)備對(duì)count進(jìn)行加1操作)。

首先通過(guò)AtomicInteger對(duì)象和內(nèi)存偏移量即可得到主存中的數(shù)據(jù)值:

var5 = this.getIntVolatile(var1, var2);

獲取到var5的值為0,然后程序會(huì)進(jìn)行判斷:

!this.compareAndSwapInt(var1, var2, var5, var5 + var4)

compareAndSwapInt()是一個(gè)本地方法,它的作用是比較并交換,即:判斷var1的值與主存中取出的var5的值是否相同,此時(shí)肯定是相同的,所以會(huì)將var5+var4的值賦值給var1,并返回true,對(duì)true取反為false,所以循環(huán)就結(jié)束了,最終方法返回1。

這是一切正常的運(yùn)行流程,然而當(dāng)發(fā)生并發(fā)時(shí),處理情況就不太一樣了,假設(shè)此時(shí)線程A執(zhí)行到了getAndAddInt()方法:

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));

    return var5;
}

線程A此時(shí)獲取到var1的值為0(var1即為共享變量AtomicInteger),當(dāng)線程A正準(zhǔn)備執(zhí)行下去時(shí),線程B搶先執(zhí)行了,線程B此時(shí)獲取到var1的值為0,var5的值為0,比較成功,此時(shí)var1的值就變?yōu)?;這時(shí)候輪到線程A執(zhí)行了,它獲取var5的值為1,此時(shí)var1的值不等于var5的值,此次加1操作就會(huì)失敗,并重新進(jìn)入循環(huán),此時(shí)var1的值已經(jīng)發(fā)生了變化,此時(shí)重新獲取var5的值也為1,比較成功,所以將var1的值加1變?yōu)?,若是在獲取var5之前別的線程又修改了主存中var1的值,則本次操作又會(huì)失敗,程序重新進(jìn)入循環(huán)。

這就是利用自旋的方式來(lái)實(shí)現(xiàn)一個(gè)樂(lè)觀鎖,因?yàn)樗鼪](méi)有加鎖,所以省下了線程調(diào)度的資源,但也要避免程序一直自旋的情況發(fā)生。

5、手寫(xiě)一個(gè)自旋鎖

public class LockDemo {

    private AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void lock() {
        // 獲取當(dāng)前線程對(duì)象
        Thread thread = Thread.currentThread();
        // 自旋等待
        while (!atomicReference.compareAndSet(null, thread)) {
        }
    }

    public void unlock() {
        // 獲取當(dāng)前線程對(duì)象
        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread, null);
    }

    static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        LockDemo lockDemo = new LockDemo();
        List<Thread> threadList = new ArrayList<>();
        for (int i = 0; i < 50; i++) {
            Thread thread = new Thread(() -> {
                lockDemo.lock();
                for (int j = 0; j < 1000; j++) {
                    count++;
                }
                lockDemo.unlock();
            });
            thread.start();
            threadList.add(thread);
        }
        // 等待線程執(zhí)行完畢
        for (Thread thread : threadList) {
            thread.join();
        }
        System.out.println(count);
    }
}

使用CAS的原理可以輕松地實(shí)現(xiàn)一個(gè)自旋鎖,首先,AtomicReference中的初始值一定為null,所以第一個(gè)線程在調(diào)用lock()方法后會(huì)成功將當(dāng)前線程的對(duì)象放入AtomicReference,此時(shí)若是別的線程調(diào)用lock()方法,會(huì)因?yàn)樵摼€程對(duì)象與AtomicReference中的對(duì)象不同而陷入循環(huán)的等待中,直到第一個(gè)線程執(zhí)行完++操作,調(diào)用了unlock()方法,該線程才會(huì)將AtomicReference值置為null,此時(shí)別的線程就可以跳出循環(huán)了。

通過(guò)CAS機(jī)制,我們能夠在不添加鎖的情況下模擬出加鎖的效果,但它的缺點(diǎn)也是顯而易見(jiàn)的:

  • 循環(huán)等待占用CPU資源
  • 只能保證一個(gè)變量的原子操作
  • 會(huì)產(chǎn)生ABA問(wèn)題

到此這篇關(guān)于淺談Java鎖機(jī)制的文章就介紹到這了,更多相關(guān)Java鎖機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 深入了解Java設(shè)計(jì)模式之UML類圖

    深入了解Java設(shè)計(jì)模式之UML類圖

    UML?即?Unified?Modeling?Language?統(tǒng)一建模語(yǔ)言,是用來(lái)設(shè)計(jì)軟件的可視化建模語(yǔ)言。本文就帶大家了解一下UML中類圖的定義與使用,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2022-11-11
  • SpringBoot中注解實(shí)現(xiàn)定時(shí)任務(wù)的兩種方式

    SpringBoot中注解實(shí)現(xiàn)定時(shí)任務(wù)的兩種方式

    這篇文章主要介紹了SpringBoot中注解實(shí)現(xiàn)定時(shí)任務(wù)的兩種方式,SpringBoot 定時(shí)任務(wù)是一種在SpringBoot應(yīng)用中自動(dòng)執(zhí)行任務(wù)的機(jī)制,通過(guò)使用Spring框架提供的@Scheduled注解,我們可以輕松地創(chuàng)建定時(shí)任務(wù),需要的朋友可以參考下
    2023-10-10
  • java案例實(shí)戰(zhàn)之字符串轉(zhuǎn)換為二進(jìn)制

    java案例實(shí)戰(zhàn)之字符串轉(zhuǎn)換為二進(jìn)制

    最近遇到個(gè)需求,要求編寫(xiě)一個(gè)程序,從鍵盤(pán)錄入一個(gè)字符串,將字符串轉(zhuǎn)換為二進(jìn)制數(shù),下面這篇文章主要給大家介紹了關(guān)于java字符串轉(zhuǎn)換為二進(jìn)制的相關(guān)資料,需要的朋友可以參考下
    2023-06-06
  • java開(kāi)發(fā)之Jdbc分頁(yè)源碼詳解

    java開(kāi)發(fā)之Jdbc分頁(yè)源碼詳解

    這篇文章主要介紹了java開(kāi)發(fā)之Jdb分頁(yè)源碼詳解,需要的朋友可以參考下
    2020-02-02
  • java正則表達(dá)式匹配網(wǎng)頁(yè)所有網(wǎng)址和鏈接文字的示例

    java正則表達(dá)式匹配網(wǎng)頁(yè)所有網(wǎng)址和鏈接文字的示例

    這篇文章主要介紹了java正則表達(dá)式匹配網(wǎng)頁(yè)所有網(wǎng)址和鏈接文字java正則表達(dá)式匹配,需要的朋友可以參考下
    2014-03-03
  • Java新手環(huán)境搭建 Tomcat安裝配置教程

    Java新手環(huán)境搭建 Tomcat安裝配置教程

    這篇文章主要為大家詳細(xì)介紹了Java新手環(huán)境搭建的相關(guān)資料,Tomcat安裝配置教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-03-03
  • JetBrains IntelliJ IDEA 2020安裝與使用教程詳解

    JetBrains IntelliJ IDEA 2020安裝與使用教程詳解

    這篇文章主要介紹了JetBrains IntelliJ IDEA 2020安裝與使用教程,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-06-06
  • Java和MySQL數(shù)據(jù)庫(kù)中關(guān)于小數(shù)的保存問(wèn)題詳析

    Java和MySQL數(shù)據(jù)庫(kù)中關(guān)于小數(shù)的保存問(wèn)題詳析

    在Java和MySQL中小數(shù)的精度可能會(huì)受到限制,如float類型的小數(shù)只能精確到6-7位,double類型也只能精確到15-16位,這篇文章主要給大家介紹了關(guān)于Java和MySQL數(shù)據(jù)庫(kù)中關(guān)于小數(shù)的保存問(wèn)題,需要的朋友可以參考下
    2024-01-01
  • scala當(dāng)中的文件操作和網(wǎng)絡(luò)請(qǐng)求的實(shí)現(xiàn)方法

    scala當(dāng)中的文件操作和網(wǎng)絡(luò)請(qǐng)求的實(shí)現(xiàn)方法

    這篇文章主要介紹了scala當(dāng)中的文件操作和網(wǎng)絡(luò)請(qǐng)求的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06
  • java優(yōu)先隊(duì)列PriorityQueue中Comparator的用法詳解

    java優(yōu)先隊(duì)列PriorityQueue中Comparator的用法詳解

    這篇文章主要介紹了java優(yōu)先隊(duì)列PriorityQueue中Comparator的用法詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-02-02

最新評(píng)論

砀山县| 江都市| 凉城县| 浠水县| 将乐县| 多伦县| 神农架林区| 南平市| 陈巴尔虎旗| 定边县| 友谊县| 汕头市| 白朗县| 玛曲县| 皮山县| 扶风县| 汕尾市| 平南县| 崇左市| 小金县| 信丰县| 博野县| 招远市| 秦皇岛市| 青海省| 元氏县| 德格县| 榆社县| 津市市| 嘉义市| 乐陵市| 桑日县| 鄂伦春自治旗| 武安市| 海盐县| 高邑县| 翼城县| 克什克腾旗| 雷山县| 清水县| 南华县|