国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

2023年Java核心技術(shù)面試第九篇(篇篇萬字精講)

這篇具有很好參考價值的文章主要介紹了2023年Java核心技術(shù)面試第九篇(篇篇萬字精講)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

目錄

十七 . 并發(fā)相關(guān)基礎(chǔ)概念

17.1 線程安全

17.2 保證線程安全的兩個方法

17.2.1 封裝

17.2.2 不可變

17.2.2.1 final 和 immutable解釋

?17.3 線程安全的基本特性

17.3.1 原子性(Atomicity)

17.3.2 可見性(Visibility)

17.3.2.1??volatile?關(guān)鍵字

17.3.2.2?synchronized?關(guān)鍵字

17.3.2.3??Lock?接口

17.3.2.3.1 解釋Lock接口:

17.3.3 有序性

?17.3.3.1?volatile?關(guān)鍵字

17.3.3.2?synchronized?關(guān)鍵字

17.3.3.3?Lock?接口

17.3.3.4?happens-before?原則

17.3.3.4.1 線程中斷規(guī)則(Thread Interruption Rule):

17.3.3.4.2??線程終止規(guī)則

17.3.4 互斥性?

17.3.4.1 synchronized?關(guān)鍵字例子:

17.3.4.2 Lock?接口例子:

十八 . synchronized底層如何實現(xiàn)?什么是鎖的升級,降級?

18.1 典型回答

18.1.1 monitorenter和monitorexit解釋:

18.1.2 Monitor實現(xiàn)

18.1.3 鎖的升級,降級

18.1.4 偏向鎖

18.1.4.1 偏向鎖的原理

18.1.4.2 偏向鎖例子

18.1.4.2.1例子詳細解釋:

18.1.4.3?偏斜鎖如何保證多線程環(huán)境下數(shù)據(jù)安全

18.1.4.4?可重入鎖或非重入鎖解釋

18.1.4.4.1 可重入鎖的例子:

18.1.4.5 互斥操作

18.1.4.6 偏向鎖的優(yōu)化點小結(jié)

18.1.5? 輕量級鎖

18.1.6 重量級鎖

18.1.7 輕量級鎖和重量級鎖的比較

18.1.8 Java是否會進行鎖的降級?

18.1.9?臨界區(qū)域(Critical Section)解釋


十七 . 并發(fā)相關(guān)基礎(chǔ)概念

?可能前面幾講,一些同學理解可以有一些困難,這一篇將進行一些并發(fā)相關(guān)概念比較模糊,我們將進行并發(fā)相關(guān)概念的補充,

17.1 線程安全

線程安全就是在多線程的環(huán)境下正確的一個概念,保證在多線程的環(huán)境下是實現(xiàn)共享的,可修改的狀態(tài)是正確性,狀態(tài)可以類比為程序里面的數(shù)據(jù)。

如果狀態(tài)不是共享的,或者不是可修改的,就不存在線程安全的問題。

17.2 保證線程安全的兩個方法

17.2.1 封裝

進行封裝,我們將對象內(nèi)部的狀態(tài)隱藏,保護起來。

17.2.2 不可變

可以進行final和immutable進行設(shè)置。

17.2.2.1 final 和 immutable解釋

finalimmutable 是 Java 中用來描述對象特性的關(guān)鍵字。

  1. final:用于修飾變量、方法和類。它的作用如下:

    • 變量:final?修飾的變量表示該變量是一個常量,不可再被修改。一旦賦值后,其值不能被改變。通常用大寫字母表示常量,并在聲明時進行初始化。
    • 方法:final?修飾的方法表示該方法不能被子類重寫(覆蓋)。
    • 類:final?修飾的類表示該類不能被繼承。
  2. immutable:指的是對象一旦創(chuàng)建后,其狀態(tài)(數(shù)據(jù))不能被修改。不可變對象在創(chuàng)建后不可更改,任何操作都不會改變原始對象的值,而是返回一個新的對象。

不可變對象的主要特點包括:

  • 對象創(chuàng)建后,其狀態(tài)無法更改。
  • 所有字段都是?final?和私有的,不可直接訪問和修改。
  • 不提供可以修改對象狀態(tài)的公共方法。

不可變對象的優(yōu)點包括:

  • 線程安全:由于對象狀態(tài)不可更改,因此多線程環(huán)境下不需要額外的同步措施。
  • 緩存友好:不可變對象的哈希值不會改變,因此可以在哈希表等數(shù)據(jù)結(jié)構(gòu)中獲得更好的性能。

?17.3 線程安全的基本特性

17.3.1 原子性(Atomicity)

指的是一系列操作要么全部執(zhí)行成功,要么全部失敗回滾。即一個操作在執(zhí)行過程中不會被其他線程打斷,保證了操作的完整性。

17.3.2 可見性(Visibility)

指的是當一個線程修改了共享變量的值后,其他線程能夠立即看到最新的值。需要通過使用?volatile?關(guān)鍵字、synchronized?關(guān)鍵字、Lock?接口等機制來確??梢娦?。

詳細解釋:

17.3.2.1??volatile?關(guān)鍵字

當一個變量被聲明為volatile時,任何對該變量的修改都會立即被其他線程可見。

當寫線程將flag值修改為true后,讀線程會立即看到最新的值,并進行相應(yīng)的操作。這是因為flag變量被聲明為volatile,確保了可見性。

public class VisibilityExample {
    private volatile boolean flag = false;
    
    public void writerThread() {
        flag = true; // 修改共享變量的值
    }
    
    public void readerThread() {
        while (!flag) {
            // 循環(huán)等待直到可見性滿足條件
        }
        System.out.println("Flag is now true");
    }
}
17.3.2.2?synchronized?關(guān)鍵字

兩個方法都使用synchronized關(guān)鍵字修飾,確保了對flag變量的原子性操作和可見性。當寫線程修改flag的值為true后,讀線程能夠立即看到最新的值。

public class VisibilityExample {
    private boolean flag = false;
    
    public synchronized void writerThread() {
        flag = true; // 修改共享變量的值
    }
    
    public synchronized void readerThread() {
        while (!flag) {
            // 循環(huán)等待直到可見性滿足條件
        }
        System.out.println("Flag is now true");
    }
}
17.3.2.3??Lock?接口

通過使用ReentrantLock實現(xiàn)了顯式的加鎖和釋放鎖操作。當寫線程獲取鎖并修改flag的值為true后,讀線程也需要獲取同樣的鎖才能看到最新的值。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class VisibilityExample {
    private boolean flag = false;
    private Lock lock = new ReentrantLock();
    
    public void writerThread() {
        lock.lock();
        try {
            flag = true; // 修改共享變量的值
        } finally {
            lock.unlock();
        }
    }
    
    public void readerThread() {
        lock.lock();
        try {
            while (!flag) {
                // 循環(huán)等待直到可見性滿足條件
            }
            System.out.println("Flag is now true");
        } finally {
            lock.unlock();
        }
    }
}
17.3.2.3.1 解釋Lock接口:

使用Lock接口進行同步時,通過持有鎖可以確保在臨界區(qū)內(nèi)的操作是互斥的,即同一時間只能有一個線程執(zhí)行臨界區(qū)的代碼。這樣可以避免多個線程同時對共享變量進行修改帶來的問題。

當讀線程在訪問共享變量之前,發(fā)現(xiàn)變量的值不符合預期,即不滿足可見性條件時,它會進入循環(huán)等待的狀態(tài)。這樣做的目的是等待寫線程將最新的值寫回共享變量,并使其對其他線程可見。

循環(huán)等待的方式可以有效地解決可見性問題。當寫線程修改共享變量的值后,它會釋放鎖。此時,讀線程能夠重新獲取鎖并再次檢查共享變量的值。如果值已經(jīng)滿足可見性條件,讀線程就能夠繼續(xù)執(zhí)行后續(xù)的操作。

需要注意的是,在循環(huán)等待的過程中,讀線程應(yīng)該使用適當?shù)牡却绞?,例如Thread.sleep()或者Lock接口提供的Condition條件對象的await()方法,以避免占用過多的CPU資源。

通過循環(huán)等待直到可見性滿足條件,可以確保讀線程在訪問共享變量時能夠看到最新的值,從而實現(xiàn)了可見性的要求。

17.3.3 有序性

指的是程序執(zhí)行的順序與預期的順序一致,不會受到指令重排序等因素的影響??梢酝ㄟ^?volatile?關(guān)鍵字、synchronized?關(guān)鍵字、Lock?接口、happens-before?原則等來保證有序性。

例子:

?17.3.3.1?volatile?關(guān)鍵字

使用volatile關(guān)鍵字修飾counter變量,確保了對變量的讀寫操作具有可見性和有序性。其他線程能夠立即看到最新的值,并且操作的順序不會被重排序。

public class OrderingExample {
    private volatile int counter = 0;
    
    public void increment() {
        counter++; // 非原子操作,但通過volatile關(guān)鍵字確保了可見性和有序性
    }
    
    public int getCounter() {
        return counter; // 獲取變量的值
    }
}
17.3.3.2?synchronized?關(guān)鍵字

使用synchronized關(guān)鍵字修飾了increment()和getCounter()方法,確保了對counter變量的原子操作,同時也提供了可見性和有序性的保證。

public class OrderingExample {
    private int counter = 0;
    
    public synchronized void increment() {
        counter++; // 原子操作,同時具備可見性和有序性
    }
    
    public synchronized int getCounter() {
        return counter; // 獲取變量的值
    }
}
17.3.3.3?Lock?接口

通過使用Lock接口實現(xiàn)了顯式的加鎖和釋放鎖操作,確保了對counter變量的原子操作,同時也提供了可見性和有序性的保證。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class OrderingExample {
    private int counter = 0;
    private Lock lock = new ReentrantLock();
    
    public void increment() {
        lock.lock();
        try {
            counter++; // 原子操作,同時具備可見性和有序性
        } finally {
            lock.unlock();
        }
    }
    
    public int getCounter() {
        return counter; // 獲取變量的值
    }
}
17.3.3.4?happens-before?原則

happens-before是并發(fā)編程中的一個概念,用于描述事件之間的順序關(guān)系。在多線程或多進程的環(huán)境中,經(jīng)常會出現(xiàn)多個事件同時發(fā)生的情況,而它們之間的執(zhí)行順序可能是不確定的。為了確保程序正確地執(zhí)行,我們需要定義一些規(guī)則來解決競態(tài)條件和并發(fā)問題。

happens-before關(guān)系用于描述事件之間的順序關(guān)系,并指定了一個事件在執(zhí)行結(jié)果上的先于另一個事件。如果一個事件A happens-before 另一個事件B,那么我們可以說事件A在時間上 "早于" 事件B,而事件B在時間上 "晚于" 事件A。

根據(jù)Java內(nèi)存模型(Java Memory Model,簡稱JMM)的規(guī)定。

happens-before關(guān)系例子:

  1. 程序順序原則(Program Order Rule):在單個線程中,按照程序的順序,前面的操作 happens-before 后面的操作。
  2. volatile變量規(guī)則(Volatile Variable Rule):對一個volatile域的寫操作 happens-before 于后續(xù)對該域的讀操作。volatile變量的寫-讀能夠確??梢娦?。
  3. 傳遞性(Transitive):如果事件A happens-before 事件B,事件B happens-before 事件C,那么可以推導出事件A happens-before 事件C。通過傳遞性,可以推斷出不同事件之間的happens-before關(guān)系。
  4. 線程啟動規(guī)則(Thread Start Rule):Thread對象的start()方法調(diào)用 happens-before 新線程的所有操作。
  5. 線程終止規(guī)則(Thread Termination Rule):線程的所有操作 happens-before 其他線程中對該線程終止檢測的操作。
  6. 線程中斷規(guī)則(Thread Interruption Rule):對線程的interrupt()方法的調(diào)用 happens-before 所被中斷線程中的代碼檢測到中斷事件的發(fā)生。

例子:

17.3.3.4.1 線程中斷規(guī)則(Thread Interruption Rule):

線程A會執(zhí)行一段任務(wù)。在線程A的任務(wù)執(zhí)行的過程中,會循環(huán)檢查中斷狀態(tài),當線程B調(diào)用線程A的interrupt()方法進行中斷時,線程A會在檢查中斷狀態(tài)的代碼處發(fā)現(xiàn)自己已被中斷并返回。這里,線程B的interrupt()調(diào)用和線程A的檢查中斷狀態(tài)的操作之間存在一個happens-before關(guān)系,保證線程B中的中斷操作能被線程A正確檢測到。

class MyTask implements Runnable {
    @Override
    public void run() {
        // 執(zhí)行任務(wù)的代碼
        // ...
        
        // 檢查中斷狀態(tài)
        if (Thread.interrupted()) {
            // 在此處被中斷
            return;
        }
        
        // 繼續(xù)執(zhí)行任務(wù)的代碼
        // ...
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread threadA = new Thread(new MyTask());
        threadA.start();
        
        // 主線程等待一段時間后中斷線程A
        Thread.sleep(1000);
        threadA.interrupt();
    }
}
17.3.3.4.2??線程終止規(guī)則

主線程首先創(chuàng)建一個子線程,并將isRunning設(shè)置為true,然后子線程進入一個死循環(huán),并在每次循環(huán)中檢查isRunning的值。主線程等待2秒后,將isRunning設(shè)置為false,終止子線程的執(zhí)行,并使用join()方法等待子線程終止。最后,主線程打印出"主線程繼續(xù)執(zhí)行"。

子線程的終止操作isRunning = false?happens-before 主線程中對isRunning的讀取操作,因此主線程能夠觀察到子線程的終止,并能夠繼續(xù)執(zhí)行。這符合線程終止規(guī)則。

public class ThreadTerminationExample {
    private static volatile boolean isRunning = true;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (isRunning) {
                // 線程執(zhí)行的工作...
            }
            System.out.println("線程已終止");
        });

        thread.start();
        Thread.sleep(2000);

        isRunning = false; // 終止線程
        thread.join(); // 等待線程終止

        System.out.println("主線程繼續(xù)執(zhí)行");
    }
}

happens-before關(guān)系的定義保證了程序執(zhí)行的可見性和有序性,為并發(fā)編程提供了一定的保證。開發(fā)人員可以利用這些規(guī)則來避免競態(tài)條件和并發(fā)問題。

17.3.4 互斥性?

指的是同一時間只允許一個線程對共享資源進行操作,其他線程必須等待。可以通過使用?synchronized?關(guān)鍵字、Lock?接口來實現(xiàn)互斥性。

17.3.4.1 synchronized?關(guān)鍵字例子:

使用synchronized關(guān)鍵字修飾了increment()和getCount()方法,這意味著同一時間只能有一個線程訪問這兩個方法。當一個線程在執(zhí)行increment()方法時,其他線程需要等待,直到當前線程執(zhí)行完畢才能繼續(xù)訪問。這樣可以保證count的操作是原子的,避免了并發(fā)訪問導致的數(shù)據(jù)沖突。

public class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}
17.3.4.2 Lock?接口例子:

使用ReentrantLock來創(chuàng)建一個鎖,并在increment()和getCount()方法中使用lock()方法獲取鎖,unlock()方法釋放鎖。這樣同一時間只允許一個線程獲取鎖并執(zhí)行代碼塊,其他線程需要等待鎖被釋放后才能繼續(xù)執(zhí)行,從而實現(xiàn)了互斥性。

無論是使用synchronized關(guān)鍵字還是Lock接口,它們都能夠?qū)崿F(xiàn)互斥性,保證多線程對共享資源的訪問是同步的,避免了數(shù)據(jù)沖突和不一致的問題。但Lock接口相比synchronized關(guān)鍵字更加靈活,可以更精細地控制鎖的獲取和釋放,提供了更多的功能。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Counter {
    private int count = 0;
    private Lock lock = new ReentrantLock();

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    public int getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
}

十八 . synchronized底層如何實現(xiàn)?什么是鎖的升級,降級?

?前面博客看了后,相信你對線程安全和如何使用基本的同步機制有了基礎(chǔ),接下了,進入synchronized底層機制。

18.1 典型回答

sychronized 代碼塊是由monitorenter/monitorexit指令實現(xiàn)的,Monitor對象是同步的基本實現(xiàn)單元。

18.1.1 monitorentermonitorexit解釋:

monitorentermonitorexit是Java字節(jié)碼指令,用于實現(xiàn)Java對象的同步鎖機制。具體來說,monitorenter指令用于獲取對象的監(jiān)視器鎖,而monitorexit指令用于釋放對象的監(jiān)視器鎖。

在Java虛擬機中,每個對象都與一個監(jiān)視器關(guān)聯(lián),可以使用synchronized關(guān)鍵字或者Object類的wait()、notify()notifyAll()方法來對對象的監(jiān)視器進行操作。在字節(jié)碼層面,monitorentermonitorexit指令就是實現(xiàn)這些操作的。

18.1.2 Monitor實現(xiàn)

Java6前,Monitor的實現(xiàn)完全依靠操作系統(tǒng)內(nèi)部的互斥鎖,因為需要從用戶態(tài)切換到內(nèi)核態(tài),同步操作是一個無差別的重量級操作。

現(xiàn)代的JDK中,JVM進行了很大的改進,提供了三種不同的Monitor實現(xiàn):

如:偏斜鎖(Biased Locking),輕量級鎖,重量級鎖,進行了性能的改進。

18.1.3 鎖的升級,降級

JVM進行優(yōu)化synchronized運行的機制,當JVM檢測到不同的競爭狀況時,會自動切換到適合的鎖實現(xiàn),這種切換就是鎖的升級,降級。

當沒有競爭出現(xiàn)時,默認使用偏斜鎖,JVM會利用CAS操作在對象頭上的Mark Word部分設(shè)置線程ID,以表示這個對象偏向于當前線程,所以并不涉及真正的互斥鎖。

這樣做的假設(shè)是基于很多應(yīng)用場景中,大部分對象生命周期中最多會被一個線程鎖定,使用偏斜鎖可以降低無競爭開銷。

18.1.4 偏向鎖

當沒有競爭出現(xiàn)時,使用偏斜鎖可以提供更好的性能表現(xiàn)。

18.1.4.1 偏向鎖的原理
  • 當一個線程訪問一個對象時,JVM會首先在對象的頭部中的 Mark Word 字段記錄當前線程的 ID,并將對象標記為“偏向鎖”。
  • 如果其他線程嘗試獲取該對象的鎖時,會發(fā)現(xiàn)該對象已經(jīng)被偏向于某個線程,此時它們會進行自旋等待,而不會立即阻塞。
  • 如果其他線程一直自旋等待,而偏斜鎖擁有者的線程也不斷訪問該對象(保持偏斜狀態(tài)),JVM會消除偏斜鎖,使得對象變?yōu)闊o鎖狀態(tài)。
  • 如果其他線程成功獲取了偏斜鎖,或者偏斜鎖擁有者的線程退出同步塊,JVM會撤銷偏斜鎖的狀態(tài),將對象重新恢復為可重入鎖或非重入鎖。
18.1.4.2 偏向鎖例子
  1. 一個多線程程序,其中有一個共享的計數(shù)器對象。在絕大多數(shù)情況下,只有一個線程會訪問該計數(shù)器對象進行自增操作,其他線程很少去改變它。這時候使用偏斜鎖會帶來明顯的性能優(yōu)勢。

    • 初始狀態(tài):計數(shù)器對象未被任何線程訪問,處于無鎖狀態(tài)。
    • 線程 A 訪問計數(shù)器對象:線程 A 首先將對象頭部中的 Mark Word 設(shè)置為自己的線程 ID,并將對象標記為“偏向鎖”。線程 A 自增計數(shù)器并完成操作。
    • 其他線程嘗試訪問計數(shù)器對象:線程 B、C、D 等嘗試獲取計數(shù)器對象的鎖,但發(fā)現(xiàn)該對象已經(jīng)被偏向于線程 A。它們會進行自旋等待,但不會立即阻塞。
    • 偏斜鎖保持狀態(tài):線程 A 再次訪問計數(shù)器對象,JVM會發(fā)現(xiàn)該對象已經(jīng)是偏斜鎖狀態(tài),并且訪問線程和持有偏斜鎖線程是同一個。因此,線程 A 可以直接訪問對象,而不需要進行互斥操作。
    • 競爭出現(xiàn):如果線程 B 嘗試獲取計數(shù)器對象的鎖,并且與偏斜鎖擁有者不是同一個線程,那么偏斜鎖就會被撤銷,變?yōu)榭芍厝腈i或非重入鎖,進而線程 B 可以成功獲取鎖。

通過偏斜鎖的優(yōu)化,當只有一個線程訪問計數(shù)器對象時,不會產(chǎn)生真正的互斥操作,避免了線程切換和鎖開銷,提高了性能。只有在其他線程嘗試獲取鎖時才會進行額外的操作,從而減少了無競爭的開銷。這種設(shè)計基于大部分對象生命周期中只被一個線程訪問的假設(shè),并且可以適用于很多應(yīng)用場景,提升程序的執(zhí)行效率。

18.1.4.2.1例子詳細解釋:

當線程 A 再次訪問計數(shù)器對象時,JVM會檢查對象的偏斜鎖狀態(tài)和持有偏斜鎖的線程是否與當前訪問線程一致。如果是一致的,表示線程 A 仍然是該對象的主要訪問者,JVM會直接允許線程 A 訪問該對象,而無需進行任何互斥操作。這樣可以避免線程切換和鎖競爭,提高程序的執(zhí)行效率。

然而,當其他線程(例如線程 B)嘗試獲取計數(shù)器對象的鎖時,JVM會檢測到存在競爭。競爭發(fā)生的條件是:嘗試獲取鎖的線程與持有偏斜鎖的線程不一致。在這種情況下,JVM會撤銷偏斜鎖的狀態(tài),將對象轉(zhuǎn)換為可重入鎖或非重入鎖。

具體而言,線程 B 嘗試獲取計數(shù)器對象的鎖時,JVM會在對象頭部中更新 Mark Word 的信息:

  • 將原先記錄持有偏斜鎖的線程 ID 清空,表示偏斜鎖的狀態(tài)被撤銷。
  • 將鎖的狀態(tài)標記為可重入鎖或非重入鎖。

此時,線程 B 成功獲取了計數(shù)器對象的鎖,并可以執(zhí)行相應(yīng)的操作。這個過程稱為偏斜鎖撤銷,對象從偏斜鎖狀態(tài)轉(zhuǎn)換為可重入鎖或非重入鎖狀態(tài)。

這種競爭的出現(xiàn)使得原先持有偏斜鎖的線程需要重新進行鎖爭用,而新的競爭線程能夠成功獲取鎖。這種機制保證了當多個線程同時需要訪問計數(shù)器對象時,能夠按照先到先得的原則進行互斥操作,避免數(shù)據(jù)被錯誤修改。

總而言之,偏斜鎖允許單線程對對象進行快速訪問,提高了程序的執(zhí)行效率。但當其他線程嘗試獲取鎖時,偏斜鎖會被撤銷,以保證多線程環(huán)境下的數(shù)據(jù)安全性。

18.1.4.3?偏斜鎖如何保證多線程環(huán)境下數(shù)據(jù)安全

假設(shè)有一個賬戶對象,包含賬戶余額信息。初始狀態(tài)下,該賬戶對象處于無鎖狀態(tài)。

  1. 線程 A 獲取偏斜鎖:
    • 線程 A 訪問賬戶對象,JVM將對象頭部中的 Mark Word 設(shè)置為自己的線程 ID,并將對象標記為“偏斜鎖”,并且記錄線程 A 是偏斜鎖的擁有者。
    • 線程 A 對賬戶余額進行修改操作,完成后釋放鎖。

此時,賬戶對象仍然是偏斜鎖狀態(tài),訪問線程和持有偏斜鎖的線程是同一個線程(線程 A),因此線程 A 可以直接訪問賬戶對象,而不需要進行互斥操作。

  1. 線程 B 嘗試獲取鎖:

    • 線程 B 也需要對賬戶對象進行修改操作,并嘗試獲取鎖。
    • JVM檢測到線程 B 和持有偏斜鎖的線程 A 不一致,表示存在競爭。
  2. 撤銷偏斜鎖:

    • JVM會撤銷賬戶對象的偏斜鎖狀態(tài),將其轉(zhuǎn)換為可重入鎖或非重入鎖。
    • 對象頭部的 Mark Word 會被更新,不再記錄持有偏斜鎖的線程 ID。

此時,線程 B 成功獲取了賬戶對象的鎖,并可以執(zhí)行相應(yīng)的操作。通過撤銷偏斜鎖,保證了在多個線程競爭下,只有一個線程能夠持有鎖并修改數(shù)據(jù),避免了數(shù)據(jù)的錯誤修改和不一致性。

偏斜鎖允許單線程(線程 A)對賬戶對象進行快速訪問,提高了程序的執(zhí)行效率。而當另一個線程(線程 B)嘗試獲取鎖時,偏斜鎖會被撤銷,確保多線程環(huán)境下的數(shù)據(jù)安全性。這種機制保證了同一時間只有一個線程能夠修改數(shù)據(jù),避免了競爭條件和數(shù)據(jù)一致性問題的產(chǎn)生。

18.1.4.4?可重入鎖或非重入鎖解釋

可重入鎖(Reentrant Lock)是一種線程同步機制,也稱為遞歸鎖。它允許一個線程在持有鎖的情況下再次請求獲取同一個鎖,而不會造成死鎖。

當一個線程獲取到可重入鎖后,可以多次重復獲取,而不會被自己所持有的鎖所阻塞。這意味著線程可以進入由同一個鎖保護的代碼塊,而不會對整個系統(tǒng)的狀態(tài)造成死鎖。

可重入鎖通過維護一個持有計數(shù)器來實現(xiàn)。線程首次獲取鎖時,計數(shù)器加一;每次釋放鎖時,計數(shù)器減一。只有計數(shù)器為零時,鎖才會完全釋放,其他線程才能獲取該鎖。

相比之下,非重入鎖(Non-Reentrant Lock)則不允許同一線程多次獲取同一個鎖。如果一個線程已經(jīng)持有一個非重入鎖,再次請求獲取同一個鎖時,會導致自己被阻塞,形成死鎖。

18.1.4.4.1 可重入鎖的例子:

有一個對象obj,它有兩個方法method1和method2,其中method2需要在獲取obj對象的鎖后才能被調(diào)用。同時,我們希望在method1中調(diào)用method2,而不會導致死鎖。

使用可重入鎖可以很好地解決這個問題,

import java.util.concurrent.locks.ReentrantLock;

public class Example {
    // 定義可重入鎖
    private ReentrantLock lock = new ReentrantLock();

    public void method1() {
        lock.lock(); // 獲取鎖
        try {
            // do something
            method2(); // 調(diào)用method2
            // do something
        } finally {
            lock.unlock(); // 釋放鎖
        }
    }

    public void method2() {
        lock.lock(); // 再次獲取鎖
        try {
            // do something
        } finally {
            lock.unlock(); // 釋放鎖
        }
    }
}

ReentrantLock類提供了可重入鎖的實現(xiàn)。method1先獲取鎖,再通過調(diào)用method2獲取同一個鎖,并在最后釋放鎖。雖然method2也獲取了鎖,但由于是在同一個線程內(nèi)部,因此不會發(fā)生死鎖。

相反,如果使用非重入鎖,則會在第二次嘗試獲取鎖時產(chǎn)生死鎖問題。

18.1.4.5 互斥操作

互斥操作指的是一種通過對共享資源的訪問進行限制,以確保在同一時間內(nèi)只有一個線程可以對該資源進行操作的機制。也就是說,當一個線程獲得了對某個資源的訪問權(quán)時,其他線程必須等待該線程釋放資源后才能繼續(xù)執(zhí)行。

互斥操作的目的是避免多個線程同時對共享資源進行修改導致數(shù)據(jù)不一致或競爭條件的發(fā)生。在多線程環(huán)境下,如果沒有互斥操作,多個線程可能同時讀取或修改共享資源的值,從而引發(fā)意料之外的錯誤和不一致性。

常見的互斥操作包括使用互斥鎖(Mutex)或信號量(Semaphore)。互斥鎖是一種排他鎖,它只允許一個線程在特定時刻獲得鎖資源,其他線程需要等待。當一個線程完成對共享資源的操作后,再釋放鎖,其他線程才能獲得鎖并繼續(xù)操作。

互斥操作的實現(xiàn)通常依賴于底層的操作系統(tǒng)提供的原子操作、臨界區(qū)或其他同步機制。這樣可以保證在并發(fā)環(huán)境中,多個線程無法同時對關(guān)鍵資源進行操作,確保了數(shù)據(jù)的一致性和線程的安全性。

互斥操作是一種通過限制并發(fā)訪問共享資源來確保數(shù)據(jù)的一致性和線程安全的機制。它能夠有效避免多個線程對共享資源的競爭和沖突,提升多線程程序的正確性和可靠性。

18.1.4.6 偏向鎖的優(yōu)化點小結(jié)

偏斜鎖的初衷是針對只有一個線程頻繁訪問同步塊的場景而設(shè)計的,偏斜鎖允許該線程連續(xù)地獲得鎖,而不需要進行互斥操作。這種連續(xù)獲取鎖的過程不會引起競爭和沖突,所以不需要額外的互斥操作。

通過偏斜鎖機制,JVM可以避免頻繁地進入和退出同步塊所帶來的性能損失。當只有一個線程在訪問同步塊時,JVM會將該對象的鎖狀態(tài)設(shè)置為偏斜鎖,并將持有偏斜鎖的線程ID記錄下來。之后,該線程再次訪問該對象時,會直接允許訪問,而無需進行互斥操作。

需要注意的是,當其他線程嘗試獲取被偏斜鎖占用的對象鎖時,偏斜鎖會自動升級為輕量級鎖或重量級鎖,從而引入互斥操作,以保證線程安全。

當一個線程再次訪問持有偏斜鎖的對象時,JVM會直接允許訪問,因為此時并沒有其他線程與之競爭。這種情況下不需要互斥操作,可以提升性能和效率。

18.1.5? 輕量級鎖

輕量級鎖(Lightweight Locking)是Java虛擬機(JVM)中一種用于實現(xiàn)線程同步的機制,旨在提高多線程并發(fā)性能。

當一個線程嘗試獲取一個對象的鎖時,JVM會將對象的鎖狀態(tài)切換為輕量級鎖狀態(tài)。輕量級鎖的核心思想是嘗試使用CAS(Compare and Swap)操作對對象頭中的Mark Word進行加鎖。以下是輕量級鎖的具體解釋:

  1. 初始狀態(tài):對象的鎖狀態(tài)為無鎖狀態(tài)(Unlocked),對象頭中的Mark Word存儲了一些額外的信息,比如指向當前線程棧中鎖記錄(Lock Record)的指針。

  2. 加鎖操作:當一個線程希望獲取該對象的鎖時,它會嘗試使用CAS操作將對象頭的Mark Word設(shè)置為自己的線程ID,表示該線程獲取到了鎖。這個CAS操作是為了確保只有一個線程能夠成功修改Mark Word。

  3. CAS操作成功:如果CAS操作成功,表示當前線程成功獲取到了對象的輕量級鎖。此時,線程可以繼續(xù)執(zhí)行臨界區(qū)代碼,不需要進一步同步操作。

  4. CAS操作失?。喝绻鸆AS操作失敗,表示有其他線程競爭同一個鎖。這時候,當前線程會嘗試自旋(Spin)來等待鎖的釋放。自旋是一種忙等待的策略,線程會反復檢查對象頭的Mark Word是否變?yōu)闊o鎖狀態(tài)。

  5. 自旋失敗:如果自旋超過了一定的次數(shù)或者達到了閾值,表示自旋失敗。這時,JVM會將對象的鎖狀態(tài)升級為重量級鎖(Heavyweight Lock)。升級為重量級鎖涉及到線程阻塞和內(nèi)核態(tài)的線程切換,比較耗費系統(tǒng)資源。

通過使用輕量級鎖,JVM避免了無競爭情況下的阻塞與喚醒,并減少了系統(tǒng)資源的消耗。只有在出現(xiàn)競爭的情況下才需要進行降級為重量級鎖,以保證線程安全性。

輕量級鎖的具體實現(xiàn)和行為可能因不同的JVM版本和配置而有所差異。此外,輕量級鎖只適用于短期的同步,對于長時間持有鎖的情況,JVM仍會將其升級為重量級鎖以避免資源浪費。

18.1.6 重量級鎖

當一個線程獲取到對象的輕量級鎖后,如果它需要長時間持有該鎖(比如執(zhí)行時間較長的臨界區(qū)代碼),JVM會將其升級為重量級鎖。這是因為長時間持有鎖可能會導致其他線程長時間等待,造成資源浪費。

理解這一點可以從以下幾個方面考慮:

  1. 自旋消耗資源:輕量級鎖使用自旋來等待鎖的釋放,自旋是一種忙等待的策略,線程反復檢查對象頭的Mark Word是否變?yōu)闊o鎖狀態(tài)。如果持有鎖的線程長時間不釋放鎖,那么其他線程會不斷自旋等待,這會導致CPU資源的浪費。

  2. 防止饑餓現(xiàn)象:在長時間持有鎖的情況下,其他線程將無法獲得鎖,這可能導致其他線程長時間等待,甚至發(fā)生饑餓現(xiàn)象。為了避免這種情況,JVM會將輕量級鎖升級為重量級鎖,使用阻塞等待的方式,確保其他線程能夠公平地獲得鎖的機會。

  3. 重量級鎖提供更強的互斥性:重量級鎖使用操作系統(tǒng)提供的底層機制(如互斥量、信號量等)來實現(xiàn)線程同步,確保只有一個線程能夠獲取到鎖。相比之下,輕量級鎖僅使用CAS操作進行加鎖,無法提供像操作系統(tǒng)級互斥那樣的嚴格互斥性。對于長時間持有鎖的情況,為了避免競爭和數(shù)據(jù)不一致的問題,JVM會將其升級為重量級鎖。

輕量級鎖適用于短期的同步,對于長時間持有鎖的情況,JVM會將其升級為重量級鎖以避免資源浪費和提供更強的互斥性,保證線程之間的公平競爭和順暢執(zhí)行。

18.1.7 輕量級鎖和重量級鎖的比較

輕量級鎖和重量級鎖都是用于實現(xiàn)線程同步的機制,但它們在性能和實現(xiàn)方式上存在差異。

在輕量級鎖中,當一個線程獲取到鎖時,它會將對象頭中的Mark Word修改為指向自己線程棧中鎖記錄的指針,并使用CAS操作進行加鎖。這種方式避免了線程阻塞和內(nèi)核態(tài)的線程切換,對于短期持有鎖的情況下具有較好的性能表現(xiàn)。

然而,當一個線程需要長時間持有鎖時,也就是執(zhí)行時間較長的臨界區(qū)代碼時,其他線程可能會長時間等待鎖的釋放,進而導致饑餓現(xiàn)象的發(fā)生。這是因為其他線程持續(xù)自旋等待鎖的釋放,而得不到執(zhí)行的機會。

為了避免饑餓現(xiàn)象和資源浪費,JVM會將輕量級鎖升級為重量級鎖。重量級鎖是使用操作系統(tǒng)提供的底層機制(如互斥量、信號量等)實現(xiàn)的,通過阻塞等待的方式,確保其他線程能夠公平地獲得鎖的機會。當一個線程持有重量級鎖時,其他線程將被阻塞,不會再執(zhí)行自旋等待,從而避免了饑餓現(xiàn)象的發(fā)生。

重量級鎖的實現(xiàn)方式可能涉及到線程的阻塞與喚醒、操作系統(tǒng)的內(nèi)核態(tài)切換等,因此會比輕量級鎖產(chǎn)生更多的開銷。所以,在長時間持有鎖的情況下,使用重量級鎖可以確保其他線程能夠公平競爭鎖的機會,但也會導致一定的性能損失。

輕量級鎖適用于短期持有鎖的情況,對于長時間持有鎖的情況,為了避免饑餓現(xiàn)象和資源浪費,JVM會將輕量級鎖升級為重量級鎖,使用阻塞等待的方式來保證公平競爭。重量級鎖雖然確保了公平性,但會帶來一定的性能損失。

18.1.8 Java是否會進行鎖的降級?

Java 中,鎖的升級是指從輕量級鎖升級為重量級鎖的過程,而鎖的降級則指從重量級鎖降級為輕量級鎖或無鎖狀態(tài)。

Java 并沒有提供直接的鎖降級機制。一旦鎖升級為重量級鎖,就不會再自動降級為輕量級鎖或無鎖狀態(tài)。

這是因為重量級鎖是通過操作系統(tǒng)提供的底層機制實現(xiàn)的,與 Java 對象頭中的標記字段無關(guān)。

只有當持有重量級鎖的線程釋放鎖后,其他線程才能獲取鎖,不會再回到輕量級鎖或無鎖狀態(tài)。

然而,在某些特定的情況下,我們可以手動進行鎖的降級操作。

比如:

如果一個線程在執(zhí)行臨界區(qū)代碼時,發(fā)現(xiàn)臨界區(qū)的代碼執(zhí)行時間很短,那么它可以選擇將重量級鎖降級為輕量級鎖或無鎖狀態(tài),以減少性能開銷。具體的做法是,線程在臨界區(qū)代碼執(zhí)行完畢后,將對象頭中的標記字段修改為指向自己線程棧中的鎖記錄,進而實現(xiàn)鎖的降級。

需要注意的是,鎖的降級需要程序員手動控制和管理,必須保證在臨界區(qū)代碼執(zhí)行期間沒有其他線程競爭同一個鎖。否則,降級操作可能會導致數(shù)據(jù)不一致或并發(fā)問題。

Java 并沒有內(nèi)置的鎖降級機制,一旦鎖升級為重量級鎖,就無法自動降級為輕量級鎖或無鎖狀態(tài)。但在特定情況下,可以手動進行鎖的降級操作,以減少性能開銷。但需要注意保證降級操作的正確性和線程安全性。

例子:

共享資源 counter 表示計數(shù)器,多個線程需要并發(fā)地對其進行操作。我們使用一個重量級鎖來保護這個計數(shù)器,初始狀態(tài)下所有線程都無法獲取這個鎖。

class Counter {
    private int count;
    private final Object lock = new Object();

    public void increment() {
        synchronized (lock) {
            // 進入臨界區(qū)域
            count++;
            // 臨界區(qū)域代碼執(zhí)行完畢,可以嘗試鎖降級
            // 將鎖降級為輕量級鎖或無鎖狀態(tài)
            // 需要手動修改對象頭中的標記字段
            lock.notifyAll();  // 喚醒等待該鎖的線程
        }
    }
    
    public int getCount() {
        return count;
    }
}

?使用了一個 synchronized 同步塊來實現(xiàn)重量級鎖,其中對 counter 進行了自增操作,并通過 lock.notifyAll() 來喚醒其他等待該鎖的線程。

現(xiàn)在,假設(shè)線程 A 獲取到了鎖,并執(zhí)行 increment 方法,對 count 自增完畢后,它選擇將鎖降級為輕量級鎖或無鎖狀態(tài):

public class Main {
    public static void main(String[] args) {
        Counter counter = new Counter();

        // 線程 A
        Thread threadA = new Thread(() -> {
            synchronized (counter.lock) {
                // 進入臨界區(qū)域
                counter.increment();
                // 臨界區(qū)域代碼執(zhí)行完畢,可以嘗試鎖降級
                // 將鎖降級為輕量級鎖或無鎖狀態(tài)
                // 需要手動修改對象頭中的標記字段
                // 假設(shè)此時沒有其他線程競爭同一個鎖
                counter.lock.notifyAll();  // 喚醒等待該鎖的線程
            }
        });

        // 線程 B
        Thread threadB = new Thread(() -> {
            synchronized (counter.lock) {
                try {
                    counter.lock.wait();  // 等待線程 A 完成臨界區(qū)域代碼
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 執(zhí)行其他操作
            }
        });

        threadA.start();
        threadB.start();

        try {
            threadA.join();
            threadB.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Count: " + counter.getCount());
    }
}

線程 A 獲取到了鎖,并執(zhí)行 increment 方法后,它選擇將對象頭中的標記字段修改為指向自己線程棧中的鎖記錄(這里是 counter.lock)。然后調(diào)用 lock.notifyAll() 喚醒其他等待該鎖的線程。

而線程 B 在獲取到鎖之后,調(diào)用 lock.wait() 進入等待狀態(tài),等待線程 A 執(zhí)行完臨界區(qū)域代碼并喚醒它。

線程 A 將鎖降級后,線程 B 能夠在沒有競爭的情況下獲取到鎖進行后續(xù)操作。

需要注意的是,鎖的降級操作必須保證在臨界區(qū)域代碼執(zhí)行期間沒有其他線程競爭同一個鎖,否則可能會導致數(shù)據(jù)不一致或并發(fā)問題。

實際應(yīng)用中需要仔細考慮鎖的升級和降級策略,并確保線程安全性。

18.1.9?臨界區(qū)域(Critical Section)解釋

指一段代碼,其中涉及對共享資源的訪問或操作。在多線程編程中,當多個線程并發(fā)地訪問共享資源時,為了保證數(shù)據(jù)的一致性和正確性,需要將對共享資源的訪問限制在臨界區(qū)域內(nèi)。

臨界區(qū)域代碼是指用于對共享資源進行訪問或操作的代碼片段。它是一個被保護起來的區(qū)域,同一時刻只能有一個線程進入并執(zhí)行其中的代碼。其他線程需要等待當前線程執(zhí)行完畢并退出臨界區(qū)域后才能進入。

臨界區(qū)域的目的是確保多個線程不會同時對共享資源進行寫操作,避免出現(xiàn)數(shù)據(jù)競爭和不一致的情況。通過限制對臨界區(qū)域的互斥訪問,可以保證在同一時間只有一個線程在執(zhí)行對共享資源的操作,從而維護數(shù)據(jù)的有效性。

例子代碼中,count++ 的操作就是一個臨界區(qū)域代碼。在 increment 方法中,使用 synchronized 關(guān)鍵字將這段代碼標記為臨界區(qū)域,以保證同一時間只有一個線程可以執(zhí)行該操作。其他線程在執(zhí)行此段代碼之前會被阻塞,直到當前線程執(zhí)行完畢并釋放鎖后才能繼續(xù)執(zhí)行。

所以,臨界區(qū)域代碼指的是多線程并發(fā)訪問共享資源時需要保護的、只允許一個線程進入執(zhí)行的代碼片段。它起到了保護共享資源的作用,確保并發(fā)操作的正確性和數(shù)據(jù)的一致性。文章來源地址http://www.zghlxwxcb.cn/news/detail-675275.html

到了這里,關(guān)于2023年Java核心技術(shù)面試第九篇(篇篇萬字精講)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔相關(guān)法律責任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • java基礎(chǔ)-----第九篇

    java基礎(chǔ)-----第九篇

    引用計數(shù)法:每個對象有一個引用計數(shù)屬性,新增一個引用時計數(shù)加1,引用釋放時計數(shù)減1,計 數(shù)為0時可以回收, 可達性分析法:從 GC Roots 開始向下搜索,搜索所走過的路徑稱為引用鏈。當一個對象到 GC Roots 沒有任何引用鏈相連時,則證明此對象是不可用的,那么虛擬機就

    2024年02月10日
    瀏覽(27)
  • 2023年MySQL核心技術(shù)面試第一篇

    目錄 前言: MySQL開篇前言補充含有前三點,先認識大概的MySQL,從下一篇開始進入MySQL的核心技術(shù)講解。 一 . MySQL開篇前言補充 存儲:一個完整的數(shù)據(jù)存儲過程是怎樣的? 1.1 數(shù)據(jù)存儲過程 ?1.1.1 創(chuàng)建MySQl 數(shù)據(jù)庫 1.1.1.1 為什么我們要先創(chuàng)建一個數(shù)據(jù)庫,而不是直接創(chuàng)建數(shù)據(jù)表

    2024年02月11日
    瀏覽(20)
  • 《Java核心技術(shù)大會2023》——AIC松鼠活動第一期

    《Java核心技術(shù)大會2023》——AIC松鼠活動第一期

    大會簡介 人工智能在22年、23年的再次爆發(fā)讓Python成為編程語言里最大的贏家;云原生的持續(xù)普及令Go、Rust等新生的語言有了進一步叫板傳統(tǒng)技術(shù)體系的資本與底氣。我們必須承認在近幾年里,Java陣營的確受到了前所未有的挑戰(zhàn),出現(xiàn)了更多更強大的競爭者。 但是,迄今Ja

    2024年02月16日
    瀏覽(24)
  • 「Java核心技術(shù)大會 2023」——小解送書第三期

    「Java核心技術(shù)大會 2023」——小解送書第三期

    目錄 共同深入探討 Java 生態(tài)!直播預約:視頻號“IT閱讀排行榜” 抽獎 大會簡介 人工智能在22年、23年的再次爆發(fā)讓Python成為編程語言里最大的贏家;云原生的持續(xù)普及令Go、Rust等新生的語言有了進一步叫板傳統(tǒng)技術(shù)體系的資本與底氣。我們必須承認在近幾年里,Java陣營的

    2024年02月09日
    瀏覽(20)
  • 「Java核心技術(shù)大會 2023」6月啟動,邀你共同探討Java生態(tài)

    「Java核心技術(shù)大會 2023」6月啟動,邀你共同探討Java生態(tài)

    ???♂? 個人主頁:@艾派森的個人主頁 ???作者簡介:Python學習者 ?? 希望大家多多支持,我們一起進步!?? 如果文章對你有幫助的話, 歡迎評論 ??點贊???? 收藏 ??加關(guān)注+ ????????人工智能在22年、23年的再次爆發(fā)讓Python成為編程語言里最大的贏家;云原生的持

    2024年02月10日
    瀏覽(34)
  • MySQL篇---第九篇

    READ UNCOMMITTED(未提交讀):事務(wù)中的修改,即使沒有提交,對其他事務(wù)也都是可見 的。會導致臟讀。 READ COMMITTED(提交讀):事務(wù)從開始直到提交之前,所做的任何修改對其他事務(wù)都是 不可見的。會導致不可重復讀。這個隔離級別,也可以叫做“不可重復讀”。 REPEATABLE

    2024年02月07日
    瀏覽(24)
  • 【第九篇:接口自動化建設(shè)】

    不要問我為什這么晚發(fā)布,這可能是我有史以來加班最晚的時候了,啊啊啊 我們之前也說過進行接口自動化建設(shè)主要是為了自動化測試服務(wù)端的邏輯,客戶端與后端交互使用的主要協(xié)議的就是http協(xié)議,這也是為什么我在開篇就和大家強調(diào)過相關(guān)的基本功的學習,學習這些基

    2024年02月03日
    瀏覽(26)
  • 「Java核心技術(shù)大會 2023」6月重磅啟動,邀你共同探討Java生態(tài)

    「Java核心技術(shù)大會 2023」6月重磅啟動,邀你共同探討Java生態(tài)

    前言 ??作者簡介: 熱愛跑步的恒川 ,致力于C/C++、Java、Python等多編程語言,熱愛跑步,喜愛音樂的一位博主。 ??本文收錄于恒川的日常匯報系列,大家有興趣的可以看一看 ??相關(guān)專欄C語言初階、C語言進階系列等,大家有興趣的可以看一看 ??Python零基礎(chǔ)入門系列,Jav

    2024年02月09日
    瀏覽(17)
  • 第九篇 API設(shè)計原則與最佳實踐

    深入淺出HTTP請求前后端交互系列專題 第一章 引言-HTTP協(xié)議基礎(chǔ)概念和前后端分離架構(gòu)請求交互概述 第二章 HTTP請求方法、狀態(tài)碼詳解與緩存機制解析 第三章 前端發(fā)起HTTP請求 第四章 前后端數(shù)據(jù)交換格式詳解 第五章 跨域資源共享(CORS):現(xiàn)代Web開發(fā)中的關(guān)鍵機制 第六篇 提

    2024年01月23日
    瀏覽(25)
  • 「Java核心技術(shù)大會 2023」6月重磅啟動,邀你共同探討Java生態(tài)【文末送書】

    「Java核心技術(shù)大會 2023」6月重磅啟動,邀你共同探討Java生態(tài)【文末送書】

    大會簡介 人工智能在22年、23年的再次爆發(fā)讓Python成為編程語言里最大的贏家;云原生的持續(xù)普及令Go、Rust等新生的語言有了進一步叫板傳統(tǒng)技術(shù)體系的資本與底氣。我們必須承認在近幾年里,Java陣營的確受到了前所未有的挑戰(zhàn),出現(xiàn)了更多更強大的競爭者。 但是,迄今Ja

    2024年02月11日
    瀏覽(17)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包