面試-多線程

synchronized [ˈsɪŋkrənaɪzd] v.同步
35. 並行和併發有什麼區別?
36. 進程和線程的區別?
37. 守護線程是什麼?
38. 建立線程有哪幾種方式?
39. 說一下 runnable 和 callable 有什麼區別?
40. 線程有哪些狀態?
41. sleep() 和 wait() 有什麼區別?
42. notify()和 notifyAll()有什麼區別?
43. 線程的 run()和 start()有什麼區別?
44. 建立線程池有哪幾種方式?
45. 線程池都有哪些狀態?
46. 線程池中 submit()和 execute()方法有什麼區別?
47. 在 java 程序中怎麼保證多線程的運行安全?(synchronized,happens-before原則)
48. 多線程鎖的升級原理是什麼?
49. 什麼是死鎖?
50. 怎麼防止死鎖?(死鎖的四個必要條件)
51. ThreadLocal 是什麼?有哪些使用場景?
52.說一下 synchronized 底層實現原理?
53. synchronized 和 volatile 的區別是什麼?
54. synchronized 和 Lock 有什麼區別?
55. synchronized 和 ReentrantLock 區別是什麼?
56. 說一下 atomic 的原理?

多線程java

35. 並行和併發有什麼區別?程序員

  • 並行是指多個事件在同一時刻發生;而併發是指多個事件在同一時間間隔發生
  • 併發:指應用可以交替執行不一樣的任務,其實併發有點相似於多線程的原理,多線程並不是是同時執行多個任務而是快速切換着執行。
  • 並行:指應用可以同時執行不一樣的任務,例如,邊吃飯邊聽音樂
  • 併發是指一個處理器同時處理多個任務。並行是指多個處理器或者是多核的處理器同時處理多個不一樣的任務。併發是邏輯上的同時發生,而並行是物理上的同時發生。如hadoop分佈式集羣。

因此併發編程的目標是充分的利用處理器的每個核,以達到醉高的處理性能。算法

36. 進程和線程的區別?編程

簡而言之,進程是程序運行和資源分配的基本單位,一個程序至少有一個進程,一個進程至少有一個或多個線程。進程在執行過程當中擁有獨li的內存單元,而多個線程共享內存資源,減小切換次數,從而效率更高。線程是進程的一個實體,同一進程中的多個線程之間能夠併發執行,是cpu調度和分派的基本單位,是比程序更小的能獨li運行的基本單位。緩存

37. 守護線程是什麼?安全

守護線程(即daemon thread),是個服務線程,準確地來講就是服務其餘的線程,這是它的做用——而其餘的線程只有一種,那就是用戶線程。因此java裏線程分2種,
一、守護線程,好比垃圾回收線程,就是最典型的守護線程。
二、用戶線程,就是應用程序裏的自定義線程。
當全部非守護線程結束時,沒有了被守護者,守護線程也就沒有工做可作了,也就沒有繼續運行程序的必要了,程序也就終止了,同時會傻死全部守護線程。 也就是說,只要有任何非守護線程還在運行,程序就不會終止。多線程

38. 建立線程有哪幾種方式?併發

①. 繼承Thread類建立線程類app

  • 繼承Thread類,並重寫該類的run方法,該run方法的方法體就表明了線程要完成的任務。所以把run()方法稱爲執行體。
  • 建立Thread子類的實例,即建立了線程對象。
  • 調用線程對象的start()方法來啓動該線程。
public class FirstThreadTest extends Thread { 
    int i=0
    //重寫run方法,run方法的方法體就是線程執行體
    public void run() { 
        for (; i < 100; i++) {
            System.out.println(getName() + ":" + i);
        }
    } 
    public static void main(String[] args) { 
        for (int i = 0; i < 100; i++) {
            //Thread.currentThread()方法返回當前正在執行的線程對象。
            //GetName()方法返回調用該方法的線程的名字。
            System.out.println(Thread.currentThread().getName() + ":" + i); 
             if (i == 50) { 
                //建立Thread子類的實例,即建立了線程對象
                new FirstThreadTest().start(); 
                new FirstThreadTest().start();
            }
        }
    }

②. 經過Runnable接口建立線程類異步

  • 實現runnable接口,並重寫該接口的run()方法,該run()方法的方法體一樣是該線程的線程執行體。
  • 建立 Runnable實現類的實例,並依此實例來建立Thread對象,該Thread對象纔是真正的線程對象。
  • 調用線程對象的start()方法來啓動該線程。
public class RunnableThreadTest implements Runnable{ 
    private int i; 
    public void run(){ 
        for(i = 0;i <100;i++) {
           System.out.println(Thread.currentThread().getName()+":"+i);
        }
     } 
     public static void main(String[] args) { 
        for(int i = 0;i < 100;i++) {
           System.out.println(Thread.currentThread().getName()+":"+i); 
           if(i==20){
                 //建立 Runnable接口實現類的實例
                 RunnableThreadTest rtt = new RunnableThreadTest(); 
                 new Thread(rtt,"新線程1").start(); 
                 new Thread(rtt,"新線程2").start();
                }
            }

        }
}

③. 經過Callable和Future建立線程

  • 實現Callable接口,並重寫call()方法,該call()方法將做爲線程執行體,而且有返回值。
  • 建立Callable實現類的實例,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的返回值。
  • 使用FutureTask對象做爲Thread對象的target(n.目標,對象)建立並啓動新線程。
  • 調用FutureTask對象的get()方法來得到子線程執行結束後的返回值。
package com.nf147.Constroller;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableThreadTest implements Callable<Integer> {


    public static void main(String[] args) {
        CallableThreadTest ctt = new CallableThreadTest();
        FutureTask<Integer> ft = new FutureTask<>(ctt);
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " 的循環變量i的值" + i);
            if (i == 20) {
                new Thread(ft, "有返回值的線程").start();
            }
        }
        try {
            System.out.println("子線程的返回值:" + ft.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
    //重寫call()方法,該call()方法將做爲線程執行體,而且有返回值
    @Override
    public Integer call() throws Exception {
        int i = 0;
        for (; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
        return i;
    }


}

建立線程的三種方式的對比
採用實現Runnable、Callable接口的方式建立多線程時,優點是:
線程類只是實現了Runnable接口或Callable接口,還能夠繼承其餘類。
在這種方式下,多個線程能夠共享同一個目標對象,因此很是適合多個相同線程來處理同一份資源的狀況,從而能夠將CPU、代碼和數據分開,造成清晰的模型,較好地體現了面向對象的思想

39. 說一下 runnable 和 callable 有什麼區別?

有點深的問題了,也看出一個Java程序員學習知識的廣度。

  • Runnable接口中的run()方法的返回值是void,它作的事情只是純粹地去執行run()方法中的代碼而已;
  • Callable接口中的call()方法是有返回值的,是一個泛型,和Future、FutureTask配合能夠用來獲取異步執行的結果。

40. 線程有哪些狀態?

線程一般都有五種狀態,建立、就緒、運行、阻塞和死亡。

  • 建立狀態。在生成線程對象並無調用該對象的start方法,這是線程處於建立狀態。
  • 就緒狀態。當調用了線程對象的start方法以後,該線程就進入了就緒狀態,等待cpu的調用,此時線程調度程序尚未把該線程設置爲當前線程。在線程運行以後,從等待或者睡眠中回來以後,也會處於就緒狀態。
  • 運行狀態。線程調度程序將處於就緒狀態的線程設置爲當前線程,此時線程就進入了運行狀態,開始運行run函數當中的代碼。
  • 阻塞狀態。線程正在運行的時候,被暫停,一般是爲了等待某個時間的發生(好比說某項資源就緒)以後再繼續運行。sleep,suspend,wait等方法均可以致使線程阻塞。
  • 死亡狀態。若是一個線程的run方法執行結束或者調用stop方法後,該線程就會死亡。對於已經死亡的線程,沒法再使用start方法令其進入就緒   

41. sleep() 和 wait() 有什麼區別?

sleep():方法是線程類(Thread)的靜態方法,讓調用線程進入睡眠狀態,讓出執行機會給其餘線程,等到休眠時間結束後,線程進入就緒狀態和其餘線程一塊兒競爭cpu的執行時間。由於sleep() 是static靜態的方法,他不能改變對象的機鎖,當一個synchronized塊中調用了sleep() 方法,線程雖然進入休眠,可是對象的機鎖沒有被釋放,其餘線程依然沒法訪問這個對象。

wait():wait()是Object類的方法,當一個線程執行到wait方法時,它就進入到一個和該對象相關的等待池,同時釋放對象的機鎖,使得其餘線程可以訪問,能夠經過notify,notifyAll方法來喚醒等待的線程

42. notify()和 notifyAll()有什麼區別?

  • 若是線程調用了對象的 wait()方法,那麼線程便會處於該對象的等待池中,等待池中的線程不會去競爭該對象的鎖。
  • 當有線程調用了對象的 notifyAll()方法(喚醒全部 wait 線程)或 notify()方法(只隨機喚醒一個 wait 線程),被喚醒的的線程便會進入該對象的鎖池中,鎖池中的線程會去競爭該對象鎖。也就是說,調用了notify後只要一個線程會由等待池進入鎖池,而notifyAll會將該對象等待池內的全部線程移動到鎖池中,等待鎖競爭。
  • 優先級高的線程競爭到對象鎖的機率大,倘若某線程沒有競爭到該對象鎖,它還會留在鎖池中,惟有線程再次調用 wait()方法,它纔會從新回到等待池中。而競爭到對象鎖的線程則繼續往下執行,直到執行完了 synchronized 代碼塊,它會釋放掉該對象鎖,這時鎖池中的線程會繼續競爭該對象鎖。

43. 線程的 run()和 start()有什麼區別?

每一個線程都是經過某個特定Thread對象所對應的方法run()來完成其操做的,方法run()稱爲線程體。經過調用Thread類的start()方法來啓動一個線程。

start()方法來啓動一個線程,真正實現了多線程運行。這時無需等待run方法體代碼執行完畢,能夠直接繼續執行下面的代碼; 這時此線程是處於就緒狀態, 並無運行。 而後經過此Thread類調用方法run()來完成其運行狀態, 這裏方法run()稱爲線程體,它包含了要執行的這個線程的內容, Run方法運行結束, 此線程終止。而後CPU再調度其它線程。

run()方法是在本線程裏的,只是線程裏的一個函數,而不是多線程的。 若是直接調用run(),其實就至關因而調用了一個普通函數而已,直接待用run()方法必須等待run()方法執行完畢才能執行下面的代碼,因此執行路徑仍是隻有一條,根本就沒有線程的特徵,因此在多線程執行時要使用start()方法而不是run()方法。

44. 建立線程池有哪幾種方式?

①. newFixedThreadPool(int nThreads)

建立一個固定長度的線程池,每當提交一個任務就建立一個線程,直到達到線程池的最達數量,這時線程規模將再也不變化,當線程發生未預期的錯誤而結束時,線程池會補充一個新的線程。

②. newCachedThreadPool()

建立一個可緩存的線程池,若是線程池的規模超過了處理需求,將自動回收空閒線程,而當需求增長時,則能夠自動添加新線程,線程池的規模不存在任何限zhi。

③. newSingleThreadExecutor()

這是一個單線程的Executor,它建立單個工做線程來執行任務,若是這個線程異常結束,會建立一個新的來替代它;它的特色是能確保依照任務在隊列中的順序來串行執行。

④. newScheduledThreadPool(int corePoolSize)

建立了一個固定長度的線程池,並且以延遲或定時的方式來執行任務,相似於Timer。

45. 線程池都有哪些狀態?

線程池有5種狀態:Running、ShutDown(中止)、Stop、Tidying(整理)、Terminated(終止)

  1. Running:線程池一旦被建立,就處於 Running 狀態,任務數爲 0,可以接收新任務,對已排隊的任務進行處理。
  2. ShutDown:不接收新任務,但能處理已排隊的任務。調用線程池的 shutdown() 方法,線程池由 Running 轉變爲 ShutDown 狀態。
  3. Stop:不接收新任務,不處理已排隊的任務,而且會中斷正在處理的任務。調用線程池的 shutdownNow() 方法,線程池由(Running 或 ShutDown ) 轉變爲 Stop 狀態。
  4. Tidying:
    ShutDown 狀態下,任務數爲 0, 其餘全部任務已終止,線程池會變爲 Tidying 狀態,會執行 terminated() 方法。線程池中的 terminated() 方法是空實現,能夠重寫該方法進行相應的處理。

    線程池在 ShutDown 狀態任務隊列爲空且執行中任務爲空,線程池就會由 ShutDown 轉變爲 TIDYING 狀態。

    線程池在 Stop 狀態,線程池中執行中任務爲空時,就會由 Stop 轉變爲 Tidying 狀態。

  5. Terminated:線程池完全終止。線程池在 Tidying 狀態執行完 terminated() 方法就會由 Tidying 轉變爲 Terminated 狀態

image

46. 線程池中 submit()和 execute()方法有什麼區別?

  • 接收的參數不同。
    execute() 參數 Runnable ;
    submit() 參數 (Runnable) 或 (Runnable 和 結果 T) 或 (Callable)
    exucute只能執行實現Runnable接口的線程,submit能夠執行實現Runnable接口或Callable接口的線程
  • submit有返回值,而execute沒有
  • submit方便Exception處理

47. 在 java 程序中怎麼保證多線程的運行安全?

線程安全在三個方面體現:

  • 原子性:提供互斥訪問,同一時刻只能有一個線程對數據進行操做,(atomic,synchronized);
  • 可見性:一個線程對共享變量的修改,另一個線程可以馬上看到(synchronized,volatile);
  • 有序性:程序執行的順序按照代碼的前後順序執行(happens-before原則)。
    致使緣由:
  • 緩存致使的可見性問題
  • 線程切換帶來的原子性問題
  • 編譯優化帶來的有序性問題

解決辦法:

  • JDK Atomic開頭的原子類、synchronized、LOCK,能夠解決原子性問題
  • synchronized、volatile、LOCK,能夠解決可見性問題
  • Happens-Before 規則能夠解決有序性問題

Happens-Before 規則以下:

  • 程序次序規則:在一個線程內,按照程序控制流順序,書寫在前面的操做先行發生於書寫在後面的操做
  • 管程鎖定規則:一個unlock操做先行發生於後面對同一個鎖的lock操做
  • volatile變量規則:對一個volatile變量的寫操做先行發生於後面對這個變量的讀操做
  • 線程啓動規則:Thread對象的start()方法先行發生於此線程的每個動做
  • 線程終止規則:線程中的全部操做都先行發生於對此線程的終止檢測
  • 線程中斷規則:對線程interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生
  • 對象終結規則:一個對象的初始化完成(構造函數執行結束)先行發生於它的finalize()方法的開始

48. 多線程鎖的升級原理是什麼?

在Java中,鎖共有4種狀態,級別從低到高依次爲:無鎖,偏向鎖,輕量級鎖和重量級鎖狀態,這幾個狀態會隨着競爭狀況逐漸升級。鎖能夠升級但不能降級

無鎖:沒有對資源進行鎖定,全部的線程都能訪問並修改同一個資源,但同時只有一個線程能修改爲功,其餘修改失敗的線程會不斷重試直到修改爲功。

偏向鎖:對象的代碼一直被同一線程執行,不存在多個線程競爭,該線程在後續的執行中自動獲取偏向鎖,指的就是偏向第1個加鎖線程,該線程是不會主動釋放偏向鎖的,只有當其餘線程嘗試競爭偏向鎖纔會被釋放。

偏向鎖的撤銷,須要在某個時間點上沒有字節碼正在執行時,先暫停擁有偏向鎖的線程,而後判斷鎖對象是否處於被鎖定狀態。若是線程不處於活動狀態,則將對象頭設置成無鎖狀態,並撤銷偏向鎖;
若是線程處於活動狀態,升級爲輕量級鎖的狀態。

輕量級鎖:輕量級鎖是指當鎖是偏向鎖的時候,被第2個線程 B 所訪問,此時偏向鎖就會升級爲輕量級鎖.
線程 B 會經過自旋的形式嘗試獲取鎖,線程不會阻塞,從而提升性能。當前只有一個等待線程,則該線程將經過自旋進行等待。
可是當自旋超過必定的次數時,輕量級鎖便會升級爲重量級鎖;當一個線程已持有鎖,另外一個線程在自旋,而此時又有第三個線程來訪時,輕量級鎖也會升級爲重量級鎖。

重量級鎖:指當有一個線程獲取鎖以後,其他全部等待獲取該鎖的線程都會處於阻塞狀態。操做系統實現線程之間的切換須要從用戶態切換到內核態,切換成本很是高

鎖升級的圖示過程: 

49. 什麼是死鎖?

死鎖是指兩個或兩個以上的進程在執行過程當中,因爲競爭資源或者因爲彼此通訊而形成的一種阻塞的現象,若無外力做用,它們都將沒法推動下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程。是操做系統層面的一個錯誤,是進程死鎖的簡稱,最先在 1965 年由 Dijkstra 在研究銀行家算法時提出的,它是計算機操做系統乃至整個併發程序設計領域最難處理的問題之一。

50. 怎麼防止死鎖?

死鎖的四個必要條件:

  • 互斥條件:進程對所分配到的資源不容許其餘進程進行訪問,若其餘進程訪問該資源,只能等待,直至佔有該資源的進程使用完成後釋放該資源
  • 請求和保持條件:進程得到必定的資源以後,又對其餘資源發出請求,可是該資源可能被其餘進程佔有,這次請求阻塞,但又對本身得到的資源保持不放
  • 不可剝奪條件:是指進程已得到的資源,在未完成使用以前,不可被剝奪,只能在使用完後本身釋放
  • 環路等待條件:是指進程發生死鎖後,若干進程之間造成一種頭尾相接的循環等待資源關係

image

這四個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之一不知足,就不會發生死鎖。

理解了死鎖的緣由,尤爲是產生死鎖的四個必要條件,就能夠儘量地避免、預防和 解除死鎖。

因此,在系統設計、進程調度等方面注意如何不讓這四個必要條件成立,如何確 定資源的合理分配算法,避免進程永jiu佔據系統資源

此外,也要防止進程在處於等待狀態的狀況下佔用資源。所以,對資源的分配要給予合理的規劃。

51. ThreadLocal 是什麼?有哪些使用場景?

Java提供ThreadLocal類來支持線程局部變量,是一種實現線程安全的方式。任何線程局部變量一旦在工做完成後沒有釋放,Java 應用就存在內存泄露的風險。

threadlocal而是一個線程內部的存儲類,能夠在指定線程內存儲數據,數據存儲之後,只有指定線程能夠獲得存儲數據
大體意思就是ThreadLocal提供了線程內存儲變量的能力,這些變量不一樣之處在於每個線程讀取的變量是對應的互相獨li的。經過get和set方法就能夠獲得當前線程對應的值。

ThreadLocal和Synchronized都是爲了解決多線程中相同變量的訪問衝突問題,不一樣的點是

  • Synchronized是經過線程等待,犧牲時間來解決訪問衝突
  • ThreadLocal是經過每一個線程單獨一份存儲空間,犧牲空間來解決衝突,而且相比於Synchronized,ThreadLocal具備線程隔離的效果,只有在線程內才能獲取到對應的值,線程外則不能訪問到想要的值。

52.說一下 synchronized 底層實現原理?

synchronized能夠保證方法或者代碼塊在運行時,同一時刻只有一個方法能夠進入到臨界區,同時它還能夠保證共享變量的內存可見性。

Java中每個對象均可以做爲鎖,這是synchronized實現同步的基礎:
jvm基於進入和退出Monitor對象來實現方法同步和代碼塊同步

  • 普通同步方法(修飾實例方法),對當前實例對象加鎖
  • 靜態同步方法(修飾靜態方法),對當前類的Class對象加鎖
  • 同步方法塊(修飾代碼塊),對synchronized括號內的對象加鎖

這裏要注意:

  • synchronized是可重入的,因此不會本身把本身鎖死
  • synchronized鎖一旦被一個線程持有,其餘試圖獲取該鎖的線程將被阻塞。
    synchronized的做用主要有如下三個:
  • 原子性:線程互斥的訪問同步代碼塊,能夠將小原子合成大原子。
  • 可見性:synchronized解鎖以前,必須將工做內存中的數據同步到主內存,其它線程操做該變量時每次均可以看到被修改後的值。
  • 有序性:一個線程的加鎖,必須等到其它線程將鎖釋放;一個線程要釋放鎖,首先要加鎖。
public class SynchronizedDemo {
    public synchronized void f(){    //爲方法加鎖
        System.out.println("Hello world");
    }
    public void g(){
        synchronized (this){        //爲代碼塊加鎖
            System.out.println("Hello world");
        }
    }
    public static void main(String[] args) {

    }
}

53. synchronized 和 volatile 的區別是什麼?
volatile([ˈvɑːlətl] adj.不穩定的)

  • volatile本質是在告訴jvm當前變量在寄存器(工做內存)中的值是不肯定的,須要從主存中讀取; synchronized則是鎖定當前變量,只有當前線程能夠訪問該變量,其餘線程被阻塞住。
  • volatile僅能使用在變量級別;synchronized則可使用在變量、方法、和類級別的。
  • volatile僅能實現變量的修改可見性,不能保證原子性;而synchronized則能夠保證變量的修改可見性和原子性。
  • volatile不會形成線程的阻塞;synchronized可能會形成線程的阻塞。
  • volatile標記的變量不會被編譯器優化;synchronized標記的變量能夠被編譯器優化。

54. synchronized 和 Lock 有什麼區別?

  • 首先synchronized是java內置關鍵字,在jvm層面,Lock是個java類;
  • synchronized沒法判斷是否獲取鎖的狀態,Lock能夠判斷是否獲取到鎖;
  • synchronized會自動釋放鎖(a 線程執行完會釋放鎖 ;b 線程執行過程當中發生異常會釋放鎖),Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),不然容易形成線程死鎖;
  • 用synchronized關鍵字的兩個線程1和線程2,若是當前線程1得到鎖,線程2線程等待。若是線程1阻塞,線程2則會一直等待下去,而Lock鎖就不必定會等待下去,若是嘗試獲取不到鎖,線程能夠不用一直等待就結束了;
  • synchronized的鎖可重入、不可中斷、非公平,而Lock鎖可重入、可判斷、可公平(二者皆可);
  • Lock鎖適合大量同步的代碼的同步問題,synchronized鎖適合代碼少許的同步問題。

55. synchronized 和 ReentrantLock 區別是什麼?

synchronized是和if、else、for、while同樣的關鍵字,ReentrantLock是類,這是兩者的本質區別。既然ReentrantLock是類,那麼它就提供了比synchronized更多更靈活的特性,能夠被繼承、能夠有方法、能夠有各類各樣的類變量,ReentrantLock比synchronized的擴展性體如今幾點上: 

  • ReentrantLock能夠對獲取鎖的等待時間進行設置,這樣就避免了死鎖 
  • ReentrantLock能夠獲取各類鎖的信息
  • ReentrantLock能夠靈活地實現多路通知 

另外,兩者的鎖機制其實也是不同的:ReentrantLock底層調用的是Unsafe的park方法加鎖,synchronized操做的應該是對象頭中mark word。

56. 說一下 atomic 的原理?

Atomic包中的類基本的特性就是在多線程環境下,當有多個線程同時對單個(包括基本類型及引用類型)變量進行操做時,具備排他性,即當多個線程同時對該變量的值進行更新時,僅有一個線程能成功,而未成功的線程能夠向自旋鎖同樣,繼續嘗試,一直等到執行成功。

Atomic系列的類中的核心方法都會調用unsafe類中的幾個本地方法。咱們須要先知道一個東西就是Unsafe類,全名爲:sun.misc.Unsafe,這個類包含了大量的對C代碼的操做,包括不少直接內存分配以及原子操做的調用,而它之因此標記爲非安全的,是告訴你這個裏面大量的方法調用都會存在安全隱患,須要當心使用,不然會致使嚴重的後果,例如在經過unsafe分配內存的時候,若是本身指定某些區域可能會致使一些相似C++同樣的指針越界到其餘進程的問題。

相關文章
相關標籤/搜索