欧美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ì)存在先后操作的問題,使得邏輯不一致,程序運(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)行,不然就阻塞掛起。 這里問題來了,如果是我們直接在代碼中添加一個(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)了,我們平常寫的java對(duì)象經(jīng)過編譯字節(jié)碼后,是會(huì)在內(nèi)存中添加一個(gè)額外的信息的,這里就涉及到另一個(gè)概念,java對(duì)象的內(nèi)存布局或者說java對(duì)象的數(shù)據(jù)結(jié)構(gòu)。

當(dāng)我們通過new關(guān)鍵字來新建一個(gè)對(duì)象時(shí),jvm會(huì)在堆內(nè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)單說到這里就ok了,這里可以看到對(duì)象在實(shí)際運(yùn)行過程中擁有鎖標(biāo)記的,這里稱為監(jiān)視器鎖,實(shí)際上對(duì)象頭的鎖信息會(huì)更多,這里只是簡(jiǎn)單概括一下。在程序中通過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)存可見性)。

使用方式如下:

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究竟鎖了誰

synchronized關(guān)鍵字的語法規(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()的定義,那么是分開競(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ì)象的基類方法,也就是說所有的對(duì)象都擁有wait()方法。一個(gè)線程調(diào)用了java的wait()方法后,當(dāng)前線程會(huì)被阻塞掛起,這里的調(diào)用指的是線程里面調(diào)用了加鎖對(duì)象的wait()方法。
  • 線程被阻塞掛起后是需要喚醒的,下面會(huì)講到喚醒方法,但是也可以調(diào)用重載方法wait(long timeout),讓線程被阻塞后超過一定時(shí)間還沒被喚醒而自動(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ì)被問到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)前線程還沒有釋放鎖的,只有調(diào)用了wait()方法才會(huì)釋放鎖。

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

消費(fèi)者會(huì)往里面拿數(shù)據(jù),沒有了數(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ì)列沒有數(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)者通過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ì)列沒有滿,生產(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ù)了,喚醒之前可能沒有數(shù)據(jù)而主動(dòng)祖寺啊自己的消費(fèi)者
                        lock.notifyAll();
                    }

                }
            }
        }).start();

    }

總結(jié)

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

相關(guān)文章

  • springboot+vue?若依項(xiàng)目在windows2008R2企業(yè)版部署流程分析

    springboot+vue?若依項(xiàng)目在windows2008R2企業(yè)版部署流程分析

    這篇文章主要介紹了springboot+vue?若依項(xiàng)目在windows2008R2企業(yè)版部署流程,本次使用jar包啟動(dòng)后端,故而準(zhǔn)備打包后的jar文件,需要的朋友可以參考下
    2022-12-12
  • Java JNDI案例詳解

    Java JNDI案例詳解

    這篇文章主要介紹了Java JNDI案例詳解,本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • java對(duì)象中什么時(shí)候適合用static修飾符踩坑解決記錄

    java對(duì)象中什么時(shí)候適合用static修飾符踩坑解決記錄

    這篇文章主要為大家介紹了java對(duì)象中什么時(shí)候適合用static修飾符踩坑解決記錄,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • Spring框架實(shí)現(xiàn)AOP添加日志記錄功能過程詳解

    Spring框架實(shí)現(xiàn)AOP添加日志記錄功能過程詳解

    這篇文章主要介紹了Spring框架實(shí)現(xiàn)AOP添加日志記錄功能過程詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-12-12
  • maven項(xiàng)目打包上傳到私有倉庫

    maven項(xiàng)目打包上傳到私有倉庫

    在項(xiàng)目開發(fā)中通常會(huì)引用其他的jar,怎樣把自己的項(xiàng)目做為一個(gè)jar包的形式發(fā)布到私服倉庫中,本文就詳細(xì)的介紹一下,感興趣的可以了解一下
    2021-06-06
  • MyBatisPlus的IService接口實(shí)現(xiàn)

    MyBatisPlus的IService接口實(shí)現(xiàn)

    MyBatisPlus是一個(gè)為MyBatis提供增強(qiáng)的工具,它通過IService接口簡(jiǎn)化了數(shù)據(jù)庫的CRUD操作,IService接口封裝了一系列常用的數(shù)據(jù)操作方法,本文就來介紹一下,感興趣的可以了解一下
    2024-10-10
  • JDK12的新特性之teeing collectors

    JDK12的新特性之teeing collectors

    這篇文章主要介紹了JDK12的新特性之teeing collectors的相關(guān)資料,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-05-05
  • Java的SPI機(jī)制以及基于SPI編程示例詳解

    Java的SPI機(jī)制以及基于SPI編程示例詳解

    這篇文章主要為大家介紹了Java的SPI機(jī)制以及基于SPI編程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-08-08
  • Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(11)

    Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(11)

    下面小編就為大家?guī)硪黄狫ava基礎(chǔ)的幾道練習(xí)題(分享)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧,希望可以幫到你
    2021-07-07
  • SpringBoot中的@RestControllerAdvice注解詳解

    SpringBoot中的@RestControllerAdvice注解詳解

    這篇文章主要介紹了SpringBoot中的@RestControllerAdvice注解詳解,RestControllerAdvice注解用于創(chuàng)建全局異常處理類,用于捕獲和處理整個(gè)應(yīng)用程序中的異常,需要的朋友可以參考下
    2024-01-01

最新評(píng)論