欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

java synchronized加鎖和釋放流程詳解

 更新時(shí)間:2025年05月16日 08:40:56   作者:程序黑板報(bào)  
這篇文章主要介紹了java synchronized加鎖和釋放流程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

為什么需要加鎖

在多線程環(huán)境中,多個(gè)線程同時(shí)運(yùn)行同一個(gè)方法時(shí),如果其中有對(duì)某一個(gè)資源就行修改處理時(shí),可能會(huì)存在先后操作的問(wèn)題,使得邏輯不一致,程序運(yùn)行的結(jié)果不時(shí)我們想要的。

線程如何加鎖

這里只講synchronized進(jìn)行加鎖,并且只進(jìn)行使用原理的闡述,其他加鎖方式使用另外的篇幅。

加鎖是為了避免多個(gè)線程同時(shí)進(jìn)行邏輯處理時(shí),可能會(huì)有數(shù)據(jù)不一致等情況從而影響程序的邏輯的準(zhǔn)確性。 所以我們可以使用一個(gè)對(duì)象,給該對(duì)象設(shè)置一個(gè)鎖狀態(tài)標(biāo)記,其他線程要進(jìn)行邏輯處理時(shí)需要把該狀態(tài)設(shè)置成功才能正常進(jìn)行,不然就阻塞掛起。 這里問(wèn)題來(lái)了,如果是我們直接在代碼中添加一個(gè)狀態(tài)標(biāo)志,那么多線程的情況下設(shè)置這個(gè)狀態(tài)下可能還是會(huì)有同時(shí)處理的情況。

這里我們可以依賴java提供的synchronized關(guān)鍵字。

java內(nèi)存布局和監(jiān)視器鎖

剛剛我們提到,可以給對(duì)象設(shè)置一個(gè)鎖狀態(tài)標(biāo)記,其實(shí)vjm已經(jīng)幫我們實(shí)現(xiàn)了,我們平常寫(xiě)的java對(duì)象經(jīng)過(guò)編譯字節(jié)碼后,是會(huì)在內(nèi)存中添加一個(gè)額外的信息的,這里就涉及到另一個(gè)概念,java對(duì)象的內(nèi)存布局或者說(shuō)java對(duì)象的數(shù)據(jù)結(jié)構(gòu)。

當(dāng)我們通過(guò)new關(guān)鍵字來(lái)新建一個(gè)對(duì)象時(shí),jvm會(huì)在堆內(nèi)存中開(kāi)辟一塊內(nèi)存存儲(chǔ)該對(duì)象實(shí)例,對(duì)象實(shí)例除了擁有我們自己定義的一些屬性方法等,還會(huì)擁有額外的其他的信息。

分為三塊:

  • 對(duì)象頭

對(duì)象頭中會(huì)存儲(chǔ)有hashcode,GC信息,鎖標(biāo)記等等。

  • 實(shí)例數(shù)據(jù)

實(shí)例數(shù)據(jù)就是我們自定義的各個(gè)字段和方法信息。

  • 填充對(duì)齊

簡(jiǎn)單理解為虛擬機(jī)中存儲(chǔ)一個(gè)對(duì)象約定對(duì)象大小為8字節(jié)的整數(shù)倍,所以如果不夠的話會(huì)額外占用一點(diǎn)空間湊數(shù)。

好了,簡(jiǎn)單說(shuō)到這里就ok了,這里可以看到對(duì)象在實(shí)際運(yùn)行過(guò)程中擁有鎖標(biāo)記的,這里稱為監(jiān)視器鎖,實(shí)際上對(duì)象頭的鎖信息會(huì)更多,這里只是簡(jiǎn)單概括一下。在程序中通過(guò)synchronize關(guān)鍵字進(jìn)行加鎖的話,jvm會(huì)幫助我們標(biāo)記該對(duì)象是由那個(gè)線程占有了,并且保證其他線程不會(huì)再擁有,只有當(dāng)線程釋放了改對(duì)象的鎖后才可以重新進(jìn)行鎖競(jìng)爭(zhēng)。

同時(shí)synchorize關(guān)鍵詞能保證操作的對(duì)象是直接從內(nèi)存中獲取的(內(nèi)存可見(jiàn)性)。

使用方式如下:

public class ThreadTest {

    public static void main(String[] args) {
        Task task = new Task();

        for (int i = 0; i< 50; i++) {
            new Thread(task).run();
        }

        System.out.println(task.getCount());
    }
}

class Task implements Runnable{
    private int count;

    private Object lock = new Object();

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    @Override
    public void run() {
        int total = 0;
        while (++total <= 10000) {
            synchronized (lock) {
                count++;
            }
        }
    }
}

synchronized究竟鎖了誰(shuí)

synchronized關(guān)鍵字的語(yǔ)法規(guī)則是定義在代碼塊中或者在定義方法時(shí)。

剛剛我們提到,java對(duì)象頭中有鎖標(biāo)記,所以下面的邏輯就是對(duì)lock這個(gè)對(duì)象進(jìn)行鎖競(jìng)爭(zhēng)

while (++total <= 10000) {
    synchronized (lock) {
        count++;
    }
}

而如果我們synchronized是在方法中定義的話,則是對(duì)當(dāng)前類的實(shí)例進(jìn)行鎖競(jìng)爭(zhēng),這里就是C1的實(shí)例對(duì)象,也即是C1 c1 = new C1()中的c1;而如果程序中還有C1 c11 = new C1()的定義,那么是分開(kāi)競(jìng)爭(zhēng)的。也即是同一個(gè)對(duì)象才進(jìn)行鎖競(jìng)爭(zhēng)。

class C1{
    private int count;

    public synchronized void run() {
        int total = 0;
        while (++total <= 10000) {
           count++;
        }
    }
}

如果對(duì)象的方法是static的,那么進(jìn)行鎖競(jìng)爭(zhēng)的是類對(duì)象,這個(gè)是jvm進(jìn)行class字節(jié)碼加載時(shí)生成的。

class C1{
    private int count;

    public static synchronized void run() {
        int total = 0;
        while (++total <= 10000) {
           count++;
        }
    }
}

至此,我們可以把監(jiān)視器鎖和synchronized關(guān)鍵字梳理了一遍。以上的重點(diǎn)信息是:java對(duì)象內(nèi)存布局和監(jiān)視器鎖以及synchronized關(guān)鍵字的處理邏輯。如果需要深入可以對(duì)各個(gè)點(diǎn)進(jìn)行往下研究。

線程的等待和喚醒

wait()方法

  • 首先我們需要了解wait()方法的繼承體系,他是在Object對(duì)象的基類方法,也就是說(shuō)所有的對(duì)象都擁有wait()方法。一個(gè)線程調(diào)用了java的wait()方法后,當(dāng)前線程會(huì)被阻塞掛起,這里的調(diào)用指的是線程里面調(diào)用了加鎖對(duì)象的wait()方法。
  • 線程被阻塞掛起后是需要喚醒的,下面會(huì)講到喚醒方法,但是也可以調(diào)用重載方法wait(long timeout),讓線程被阻塞后超過(guò)一定時(shí)間還沒(méi)被喚醒而自動(dòng)喚醒。

notify()方法

  • notify()方法也是繼承于Object對(duì)象。當(dāng)某個(gè)線程調(diào)用了加鎖對(duì)象的notify方法后,會(huì)喚醒之前在該對(duì)象進(jìn)行獲取監(jiān)視器鎖時(shí)失敗而被阻塞的線程,如果有多個(gè)線程同時(shí)被阻塞,notify()方法只會(huì)有一個(gè)線程被喚醒,如果需要喚醒全部,則可以調(diào)用notifyAll()方法。

所以面試中會(huì)被問(wèn)到wait和notify的作用,可以側(cè)重的知識(shí)點(diǎn)是:

  • 1.調(diào)用wait之前一定是獲取到鎖的,所以要保證在synchronized塊中。
  • 2.調(diào)用wait后會(huì)釋放該對(duì)象的鎖。
  • 3.調(diào)用notify()方法也要是獲取鎖, 也要保證在synchronized塊中。
  • 4.調(diào)用notify()方法喚醒一個(gè)線程,調(diào)用notifyAll()方法喚醒全部被阻塞線程。
  • 5.調(diào)用notify()或者notifyAll()方法只是喚醒了其他被阻塞的線程,他們有了重新競(jìng)爭(zhēng)鎖的條件,但是當(dāng)前線程還沒(méi)有釋放鎖的,只有調(diào)用了wait()方法才會(huì)釋放鎖。

用一個(gè)生產(chǎn)者消費(fèi)者模型來(lái)看看wait和notify的用法。生產(chǎn)者消費(fèi)者模型可以簡(jiǎn)單理解為有一個(gè)容器,當(dāng)里面沒(méi)有數(shù)據(jù)時(shí)生產(chǎn)者會(huì)往里面添加數(shù)據(jù),滿了則暫停當(dāng)前的工作等待消費(fèi)者消費(fèi)數(shù)據(jù)后通知他繼續(xù)添加。

消費(fèi)者會(huì)往里面拿數(shù)據(jù),沒(méi)有了數(shù)據(jù)則暫停工作等待生產(chǎn)者生產(chǎn)了數(shù)據(jù)并通知他繼續(xù)消費(fèi)。

public static void main(String[] args) {
        Object lock = new Object();
        AtomicInteger counter = new AtomicInteger(0);
        Queue<Integer> queue = new LinkedList<>();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    while (true) {
                        //如果隊(duì)列沒(méi)有數(shù)據(jù),調(diào)用wait()方法,阻塞自己
                        if (queue.isEmpty()) {
                            try {
                                System.out.println("消費(fèi)者線程阻塞");
                                lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }

                        //如果隊(duì)列不為空,消費(fèi)數(shù)據(jù);如果線程被生產(chǎn)者通過(guò)notifyAll()方法喚醒后,線程重新獲取到鎖時(shí)是從這里執(zhí)行的
                        System.out.println("消費(fèi)者線程消費(fèi)數(shù)據(jù): " + queue.poll());
                        //消費(fèi)者消費(fèi)后,喚醒可能由于之前隊(duì)列滿了而主動(dòng)阻塞自己的生產(chǎn)者
                        lock.notifyAll();

                    }

                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    while (true) {
                        //如果隊(duì)列數(shù)據(jù)滿了,調(diào)用wait()方法,阻塞自己
                        if (queue.size() > 10) {
                            System.out.println("生產(chǎn)者線程阻塞");
                            try {
                                lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }

                        //如果隊(duì)列沒(méi)有滿,生產(chǎn)數(shù)據(jù); 如果被其他線程喚醒,在下次獲取到鎖的時(shí)候生產(chǎn)數(shù)據(jù)
                        System.out.println("生產(chǎn)者線程生產(chǎn)數(shù)據(jù)");
                        queue.add(counter.incrementAndGet());

                        //隊(duì)列有數(shù)據(jù)了,喚醒之前可能沒(méi)有數(shù)據(jù)而主動(dòng)祖寺啊自己的消費(fèi)者
                        lock.notifyAll();
                    }

                }
            }
        }).start();

    }

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • 解決java使用file.createNewFile()創(chuàng)建文件時(shí)報(bào)錯(cuò)目錄不存在的問(wèn)題

    解決java使用file.createNewFile()創(chuàng)建文件時(shí)報(bào)錯(cuò)目錄不存在的問(wèn)題

    這篇文章主要介紹了解決java使用file.createNewFile()創(chuàng)建文件時(shí)報(bào)錯(cuò)目錄不存在的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-06-06
  • javamail 發(fā)送郵件的實(shí)例代碼分享

    javamail 發(fā)送郵件的實(shí)例代碼分享

    今天學(xué)習(xí)了一下JavaMail,javamail發(fā)送郵件確實(shí)是一個(gè)比較麻煩的問(wèn)題。為了以后使用方便,自己寫(xiě)了段代碼,打成jar包,以方便以后使用
    2013-08-08
  • Java中CyclicBarrier的用法分析

    Java中CyclicBarrier的用法分析

    CyclicBarrier和CountDownLatch一樣,都是關(guān)于線程的計(jì)數(shù)器。用法略有不同,測(cè)試代碼如下:
    2013-03-03
  • 詳談Java中的二進(jìn)制及基本的位運(yùn)算

    詳談Java中的二進(jìn)制及基本的位運(yùn)算

    下面小編就為大家?guī)?lái)一篇詳談Java中的二進(jìn)制及基本的位運(yùn)算。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-07-07
  • IDEA之項(xiàng)目run按鈕為灰色,無(wú)法運(yùn)行問(wèn)題

    IDEA之項(xiàng)目run按鈕為灰色,無(wú)法運(yùn)行問(wèn)題

    這篇文章主要介紹了IDEA之項(xiàng)目run按鈕為灰色,無(wú)法運(yùn)行問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • Java輸入數(shù)據(jù)的知識(shí)點(diǎn)整理

    Java輸入數(shù)據(jù)的知識(shí)點(diǎn)整理

    在本篇文章里小編給大家整理的是關(guān)于Java如何輸入數(shù)據(jù)的相關(guān)知識(shí)點(diǎn)內(nèi)容,有興趣的朋友們學(xué)習(xí)參考下。
    2020-01-01
  • Mybatis實(shí)戰(zhàn)教程之入門到精通(經(jīng)典)

    Mybatis實(shí)戰(zhàn)教程之入門到精通(經(jīng)典)

    MyBatis是支持普通SQL查詢,存儲(chǔ)過(guò)程和高級(jí)映射的優(yōu)秀持久層框架,通過(guò)本文給大家介紹Mybatis實(shí)戰(zhàn)教程之入門到精通,對(duì)mybatis實(shí)戰(zhàn)教程相關(guān)知識(shí)感興趣的朋友一起學(xué)習(xí)吧
    2016-01-01
  • Java學(xué)習(xí)教程之定時(shí)任務(wù)全家桶

    Java學(xué)習(xí)教程之定時(shí)任務(wù)全家桶

    這篇文章主要給大家介紹了關(guān)于Java學(xué)習(xí)教程之定時(shí)任務(wù)全家桶的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • 一文搞懂Java的ThreadPoolExecutor原理

    一文搞懂Java的ThreadPoolExecutor原理

    都說(shuō)經(jīng)典的就是好的,這句話放在Java的ThreadPoolExecutor上那是一點(diǎn)都沒(méi)錯(cuò),像現(xiàn)在數(shù)據(jù)庫(kù)連接的池化實(shí)現(xiàn),或者像Tomcat這種WEB服務(wù)器的線程管理,處處都有著ThreadPoolExecutor的影子,本篇文章將結(jié)合源碼實(shí)現(xiàn),對(duì)ThreadPoolExecutor的原理進(jìn)行一個(gè)深入學(xué)習(xí)
    2023-06-06
  • 分享7款開(kāi)源Java反編譯工具

    分享7款開(kāi)源Java反編譯工具

    今天我們要來(lái)分享一些關(guān)于Java的反編譯工具,反編譯聽(tīng)起來(lái)是一個(gè)非常高上大的技術(shù)詞匯,通俗的說(shuō),反編譯是一個(gè)對(duì)目標(biāo)可執(zhí)行程序進(jìn)行逆向分析,從而得到原始代碼的過(guò)程。尤其是像.NET、Java這樣的運(yùn)行在虛擬機(jī)上的編程語(yǔ)言,更容易進(jìn)行反編譯得到源代碼
    2014-09-09

最新評(píng)論