八、多線程為什么會出現安全問題


前言:

  在前面我們主要介紹了一下線程的創建,一些枯燥的概念,以及線程間如何通信和多線程存在線程安全的問題,那么為什么多線程在執行的時候會造成安全問題呢,這一問題我們並沒有深入的進入下去,下面我們來了解一下所謂的線程安全倒地時怎么來的。

一、內存模型簡述

  java內存模型之前專門寫過一篇總結,雖然都是拿網上的資料東拼西湊的,也多次的去掌握這方面知識,但一些東西還是不了解,網上有很多資料,這里不久過多贅述,知識簡單的描述一下。

  了解的同學都知道java內存模型被分為了五個區域,程序計數器、堆、虛擬機棧、本地方法棧以及方法區,理論上方法區也是屬於堆中的一部分,只不過方法區是堆中的一塊永久區域,也就是垃圾回收不是很頻繁,但絕不是不進行垃圾回收,而堆中的垃圾回收則相對頻繁的進行,我們稍微來看一下五個區域的作用,下面上網上的一張分區圖。

  

  程序計數器:

    我們都知道線程之間的執行時並發的,既然是並發的那就存在頻繁切換,如果線程A執行一段代碼執行到一半,線程搶到了CPU執行權,當線程B執行完后,如何能夠找到線程A被搶斷執行到的位置呢,這就是計數器的作用。

    也就因此每個線程都對一個程序計數器,且是該線程獨享的,也就是私有(若不是私有的則找不到被打斷是哪個線程的哪一行代碼),而計數器就記錄了線程正在執行的內存地址,以確保被打斷是能夠回到原來的地方再次執行。

  虛擬機棧:

    虛擬機棧也叫本地方法棧,在java虛擬機中每一個線程都會對應一個棧,也就是說一個線程在執行時在創建程序計數器的同時會創建一個棧,

    每個java虛擬機棧則是由多個棧幀組成的,而每個棧幀則對應了一個方法(通常來說線程里面調用的都是多個方法,那么這里面的每一個方法都對應一個棧幀),棧幀在方法運行時,創建並入棧,方法執行完畢后該棧幀會彈出棧幀中的元素作為返回值,並且棧幀也會被清除,而虛擬機棧中主要存放局部變量和引用型變量,因為是每一個線程都會創建一個棧,所以是私有的。

  方法區:

    方法區則是java堆中的永久區域,主要存放了一些類的信息,例如:類的全限定名、修飾符,方法名稱,static靜態常量(注:指定義就已經賦值)、final類型的常量,類中的字段名稱,方法的參數等,因此方法區是所有線程共享的。

  常量池: 

    常量池是屬於方法區的一塊內存,又分為運行時常量池和靜態常量池,這里就不細說了;

    常量池中存儲的數據可以分成兩部分,一類是是字面量(指:字符串、final常量等)、另一類則是引用量(指:類、接口,方法和字段名稱以及描述),常量池則是在編譯期間就已經確定被保存在已經編譯好的.class文件中

  本地方法棧:

    本地方法棧和虛擬機棧類似,只不過它是專門用來執行native方法的,而虛擬機棧是執行java的方法的,而native方法通常調用的是java一些最底層的東西,例如C語言等;

  堆內存:

    堆內存用於存放由new創建的對象和數組,每個實體都有一個內存地址值,實體中的變量都有默認初始化值,即使實體不在使用了,不把實體的引用指向null,垃圾回收也不會執行回收

二、線程安全問題的分析:

  上面我們大致了解了一下內存的分布,那么多線程在運行過程是在哪一塊出了問題,而導致輸出的結果不正確的,來更具傳智播客老師講課的一張圖來分析一下流程:

  

 

 

   如上如果我們寫了一個MoneyDemo類,里面有一個main方法,來看一下它的執行過程會使什么樣的:

    第一步:那么類加載器會將MoneyDemo類的.class文件加入到內存中,方法區會存入我們的類的全限定名、修飾關鍵字、里面的方法名稱、參數、返回值等,

    第二步:java的類執行引擎就會去方法區找到我們的MoneyDemo類的信息,找要啟動的方法就是main方法,為main方法創建虛擬機棧和程序計數器,同時將new Object()則是存入到堆內存中個,而Object obj 則會被存入棧內;

    第三步:在java虛擬機棧找到main方法的棧幀后,請求CUP資源,那么虛擬機就會將java虛擬機棧當前要執行main方法的信息存入高速緩存寄存器中,CUP獲取高速緩存內的信息去執行代碼。

    第四步:若這個時候我們Object對象進行更改,更改的信息會先存入高速緩存,然后根據棧幀找到虛擬機棧中的main方法,然后將更改的內容同步不到堆內存中,

    那么這時候問題就出現了,如果有兩個線程A和B,線程A正修改了Object對象的內容,還沒有來的即同步到主內存,而線程B卻在讀堆中的Object對象,那么這時候讀出來的信息就是錯誤的數據,至此我們的多線程為什么會出現安全問題也就找到了。

三、結論:

    看到這里想必都明白了,多線程下出現安全問題,就是存在多條線程操作同一數據,而CPU執行時先將主內存中的數據拷貝一份作緩存,之后的操作不在從請求主內存中拿數據而是從緩存中獲取數據,如果執行完后有更改再同步回主內存,這時候如果存在多條線程操作,就會出現主內存和緩存中數據的不一致,而導致了最終結果的錯誤,而單線程情況下則完全不會出現這一情況。    

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM