亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

java synchronized加鎖和釋放流程詳解

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

為什么需要加鎖

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

線程如何加鎖

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

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

這里我們可以依賴java提供的synchronized關鍵字。

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

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

當我們通過new關鍵字來新建一個對象時,jvm會在堆內存中開辟一塊內存存儲該對象實例,對象實例除了擁有我們自己定義的一些屬性方法等,還會擁有額外的其他的信息。

分為三塊:

  • 對象頭

對象頭中會存儲有hashcode,GC信息,鎖標記等等。

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

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

  • 填充對齊

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

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

同時synchorize關鍵詞能保證操作的對象是直接從內存中獲取的(內存可見性)。

使用方式如下:

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ī)則是定義在代碼塊中或者在定義方法時。

剛剛我們提到,java對象頭中有鎖標記,所以下面的邏輯就是對lock這個對象進行鎖競爭

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

而如果我們synchronized是在方法中定義的話,則是對當前類的實例進行鎖競爭,這里就是C1的實例對象,也即是C1 c1 = new C1()中的c1;而如果程序中還有C1 c11 = new C1()的定義,那么是分開競爭的。也即是同一個對象才進行鎖競爭。

class C1{
    private int count;

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

如果對象的方法是static的,那么進行鎖競爭的是類對象,這個是jvm進行class字節(jié)碼加載時生成的。

class C1{
    private int count;

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

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

線程的等待和喚醒

wait()方法

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

notify()方法

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

所以面試中會被問到wait和notify的作用,可以側重的知識點是:

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

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

消費者會往里面拿數(shù)據(jù),沒有了數(shù)據(jù)則暫停工作等待生產者生產了數(shù)據(jù)并通知他繼續(xù)消費。

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) {
                        //如果隊列沒有數(shù)據(jù),調用wait()方法,阻塞自己
                        if (queue.isEmpty()) {
                            try {
                                System.out.println("消費者線程阻塞");
                                lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }

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

                    }

                }
            }
        }).start();

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

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

                        //隊列有數(shù)據(jù)了,喚醒之前可能沒有數(shù)據(jù)而主動祖寺啊自己的消費者
                        lock.notifyAll();
                    }

                }
            }
        }).start();

    }

總結

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

相關文章

  • 解決java使用file.createNewFile()創(chuàng)建文件時報錯目錄不存在的問題

    解決java使用file.createNewFile()創(chuàng)建文件時報錯目錄不存在的問題

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

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

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

    Java中CyclicBarrier的用法分析

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

    詳談Java中的二進制及基本的位運算

    下面小編就為大家?guī)硪黄斦凧ava中的二進制及基本的位運算。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-07-07
  • IDEA之項目run按鈕為灰色,無法運行問題

    IDEA之項目run按鈕為灰色,無法運行問題

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

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

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

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

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

    Java學習教程之定時任務全家桶

    這篇文章主要給大家介紹了關于Java學習教程之定時任務全家桶的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-11-11
  • 一文搞懂Java的ThreadPoolExecutor原理

    一文搞懂Java的ThreadPoolExecutor原理

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

    分享7款開源Java反編譯工具

    今天我們要來分享一些關于Java的反編譯工具,反編譯聽起來是一個非常高上大的技術詞匯,通俗的說,反編譯是一個對目標可執(zhí)行程序進行逆向分析,從而得到原始代碼的過程。尤其是像.NET、Java這樣的運行在虛擬機上的編程語言,更容易進行反編譯得到源代碼
    2014-09-09

最新評論