1. 程式人生 > >ArrayList在多執行緒呼叫Add()新增元素時的下標越界問題(java.lang.ArrayIndexOutOfBoundsException)

ArrayList在多執行緒呼叫Add()新增元素時的下標越界問題(java.lang.ArrayIndexOutOfBoundsException)

最近在看《實戰Java虛擬機器》一書,看到有關鎖與併發章節時,看到如下一個多執行緒使用ArrayList的例子:



       兩個執行緒t1和t2同時向numberList中新增資料,由於ArrayList是執行緒不安全的,因此會導致新增的資料有錯誤,這個我還是能理解的,但是它報的確是如下錯誤:


       我就有點理解不了了,ArrayList不是自動擴容、沒有長度限制嗎,為什麼還會出現陣列下標越界這種錯誤呢?

       為了便於分析,我對程式碼進行了一點點修改:


  執行結果為:







有時還會出現null,


       帶著種種不解,來看ArrayList新增流程:

       首先,ArrayList是基於陣列實現的,是一個動態陣列,其容量能自動增長,類似於C語言中的動態申請記憶體,動態增長記憶體。

       對於ArrayList而言,它實現List介面、底層使用陣列儲存所有元素。其操作基本上是對陣列的操作。

       1、程式中報錯的 at java.util.ArrayList.elementData(ArrayList.java:400) 和 at java.util.ArrayList.add(ArrayList.java:441),它們同屬Add()方法。   

       原始碼如下:                                                                                                                                                                       

新增操作,首先會呼叫ensureCapacityInternal(size + 1),其作用為保證陣列的容量始終夠用,其中size是elementData陣列中元組的個數,初始為0。


ensureCapacityInternal()函式中,用if判斷,如果陣列沒有元素,給陣列一個預設大小,會選擇例項化時的值與預設大小中較大值,然後呼叫ensureExplicitCapacity()。


    函式體中,modCount是陣列發生size更改的次數。然後if判斷,如果陣列長度小於預設的容量10,則呼叫擴大陣列大小的方法grow()。
    函式grow()解釋了基於陣列的ArrayList是如何擴容的。陣列進行擴容時,會將老陣列中的元素重新拷貝一份到新的陣列中,每次陣列容量的增長大約是其原容量的1.5倍。
       接下來回到Add()函式,繼續執行,elementData[size++] = e; 這行程式碼就是問題所在,當新增一個元素的時候,它可能會有兩步來完成:1. 在 elementData[Size] 的位置存放此元素;2. 增大 Size 的值。
       在單執行緒執行的情況下,如果 Size = 0,新增一個元素後,此元素在位置 0,而且 Size=1;
       而如果是在多執行緒情況下,比如有兩個執行緒,執行緒 A 先將元素存放在位置 0。但是此時 CPU 排程執行緒A暫停,執行緒 B 得到執行的機會。執行緒B也向此 ArrayList 新增元素,因為此時 Size 仍然等於 0 (注意哦,我們假設的是新增一個元素是要兩個步驟哦,而執行緒A僅僅完成了步驟1),所以執行緒B也將元素存放在位置0。然後執行緒A和執行緒B都繼續執行,都增加 Size 的值。那好,我們來看看 ArrayList 的情況,元素實際上只有一個,存放在位置 0,而 Size 卻等於 2。這就是“執行緒不安全”了。這就解釋了為何集合中會出現null。
 但是陣列下標越界還不能僅僅依靠這個來解釋。我們觀察發生越界時的陣列下標,分別為10、15、22、33、49和73。結合前面講的陣列自動機制,陣列初始長度為10,第一次擴容為15=10+10/2,第二次擴容22=15+15/2,第三次擴容33=22+22/2...以此類推,我們不難發現,越界異常都發生在陣列擴容之時。
       由此給了我想法,我猜想是,由於沒有該方法沒有同步,導致出現這樣一種現象,用第一次異常,即下標為15時的異常舉例。當集合中已經添加了14個元素時,一個執行緒率先進入add()方法,在執行ensureCapacityInternal(size + 1)時,發現還可以新增一個元素,故陣列沒有擴容,但隨後該執行緒被阻塞在此處。接著另一執行緒進入add()方法,執行ensureCapacityInternal(size + 1),由於前一個執行緒並沒有新增元素,故size依然為14,依然不需要擴容,所以該執行緒就開始新增元素,使得size++,變為15,陣列已經滿了。而剛剛阻塞在elementData[size++] = e;語句之前的執行緒開始執行,它要在集合中新增第16個元素,而陣列容量只有15個,所以就發生了陣列下標越界異常!

相關推薦

ArrayList執行呼叫Add()新增元素時的下越界問題(java.lang.ArrayIndexOutOfBoundsException)

最近在看《實戰Java虛擬機器》一書,看到有關鎖與併發章節時,看到如下一個多執行緒使用ArrayList的例子:        兩個執行緒t1和t2同時向numberList中新增資料,由於Arr

setlocale 執行呼叫引發程式crash

最近的一個專案,由於要讀寫中文文字,所以使用了以下程式碼:     char* old_locale = _strdup(setlocale(LC_CTYPE, NULL)); setlocale(LC_CTYPE, "chs"); CS

python中的執行threading之新增執行:Thread()

百度百科:多執行緒 多執行緒(英語:multithreading),是指從軟體或者硬體上實現多個執行緒併發執行的技術。具有多執行緒能力的計算機因有硬體支援而能夠在同一時間執行多於一個執行緒,進而提升整體處理效能。具有這種能力的系統包括對稱多處理機、多核心處理器以及晶片級多處理(Chi

java 如何使用執行呼叫類的靜態方法?

  1.情景展示   靜態方法內部實現:將指定內容生成圖片格式的二維碼;   如何通過多執行緒實現? 2.分析   之所以採用多執行緒,是為了節省時間  3.解決方案   準備工作   logo檔案     將生成的檔案儲存在F

執行呼叫系統COM元件的體會(CoInitialize)

多執行緒呼叫COM元件的體會(CoInitialize) 呼叫任何COM元件之前,你必須首先初始化COM套件環境,即呼叫CoInitialize或CoInitializeEx。COM套件環境線上程的生存週期內有效,執行緒退出前需要呼叫CoUninitializ

C# 執行呼叫靜態方法或者靜態例項中的同一個方法-方法內部的變數是執行安全的

 C#  多執行緒呼叫靜態方法或者靜態例項中的同一個方法-方法內部的變數是執行緒安全的       using System;using System.Threading;using System.Threading.Tasks;using Sys

C#執行呼叫有參的方法

Thread (ParameterizedThreadStart) 初始化 Thread 類的新例項,指定允許物件線上程啟動時傳遞給執行緒的委託。    Thread (ThreadStart) 初始化 Thread 類的新例項。   由 .NET Compact Fra

C# Webbrowser 常用方法及執行呼叫

public partial class htmlElement { //根據Name獲取元素 public HtmlElement GetElement_Name(WebBrowser wb, string Name) { H

C# 使用委託實現執行呼叫窗體的四種方式

1、方法一:使用執行緒      功能描述:在用c#做WinFrom開發的過程中。我們經常需要用到進度條(ProgressBar)用於顯示進度資訊。這時候我們可能就需要用到多執行緒,如果不採用多執行緒控制進度條,視窗很容易假死(無法適時看到進度資訊)。下面

C++ 執行呼叫Python指令碼

由於Python直譯器有全域性解釋所GIL的原因,導致在同一時刻只能有一個執行緒擁有直譯器,所以在C++多執行緒呼叫python指令碼時,需要控制GIL,執行緒獲取GIL。 在主執行緒中初始化Python直譯器環境,程式碼如下: { Py_Initialize()

執行呼叫單例類中的方法會不會造成執行安全問題

區域性變數不會受多執行緒影響 成員變數會受到多執行緒影響 多個執行緒應該是呼叫的同一個物件的同一個方法: 如果方法裡無成員變數,那麼不受任何影響 如果方法裡有成員變數,只有讀操作,不受影響                      存在寫操作,考慮多執行緒影響值 當多個執行

使用java語言,利用執行呼叫WebService進行資料處理

因工作原因,需要將一個表(tbA)中的所有資料,根據user_id,去請求webserive獲取相關的資料,然後插入到另外的一張表(tbB)中,供他人使用。不過這個表中的資料不少有78萬條左右,而這樣的大批量資料操作,還不能白天執行。只能在夜裡,等伺服器負荷低的時候進

Boost asio學習筆記之一—— 使用strand支援執行呼叫service_io的方法

asio是一個跨平臺的網路庫,可以作為boost的一部分,也可以使用獨立的asio部分。這裡記錄學習的筆記,作為參考。 感覺asio的關鍵就是io_service物件。所有的非同步同步都跟這個有關。多執行緒方式下要asio::strand來解決。下面用timmer.5為例在進行研究。 (1)首先建立專案。

cudaMemGetInfo獲取的可用視訊記憶體和全部視訊記憶體有問題和實際有出入?CPU執行呼叫GPU

我在vs上執行cuda程式,用cudaMemGetInfo獲得的總的視訊記憶體是3g,但是實際應該有4g。可用的視訊記憶體是3028M,那就是用了44M,但是用GPU-Z和nvidia-smi都顯示用了800多M,不知道為什麼是這樣?這種問題很難定量。例如一臺有 32GB 記

cocos2dx 執行呼叫ui主執行

Director::getInstance()->getScheduler()->performFunctionInCocosThread([&]() { ////////////////////// To Do Something !! ///////

高效能、高併發TCP伺服器(執行呼叫libevent)

本文講述的TCP伺服器是模仿memcache中的TCP網路處理框架,其中是基於libevent網路庫的。 主執行緒只處理監聽客戶端的連線請求,並將請求平均分配給子執行緒。 子執行緒處理與客戶端的連線以及相關業務。 每個子執行緒有一個“連線”佇列。每個“連線”有一個“反饋”佇列。 先上個流程圖,要上班了

【OS大作業】用執行統計txt檔案中字元個數(Java實現)

問題描述 給定一個txt檔案,利用不同個數的執行緒查詢檔案中某字元的個數,探究執行緒個數與查詢時間的關係。 本作業程式碼使用JAVA實現,版本為10.0.2,使用的IDE為Eclipse4.9.0. 結果測試所用的txt檔案內容為英文,編碼格式為UTF-8。 原始碼 第一版程式碼:(

Java執行小結 深入理解JVM—JVM記憶體模型 Java Integer(-128~127)值的==和equals比較產生的思考

  相關資料 -------------------------------------------------------------------------------------  Java多執行緒demo https://github.com/Beerkay/JavaMul

18年執行視訊教程併發程式設計網際網路架構視訊java面試知識-張顏源-專題視訊課程...

18年多執行緒視訊教程併發程式設計網際網路架構視訊java面試知識—39人已學習 課程介紹        2018年10月併發程式設計及原理視訊培訓教程:囊括執行緒基礎知識、執行緒安全問題、JDK鎖機制、執行緒建通訊、併發工具、執行緒池等詳細知識點,面試高頻知識點原始碼深入剖

執行(八): Vector與ArrayList

ArrayList不允許寫操作沒執行完就執行讀操作,正在讀的時候不允許去寫,必須寫完再讀,讀完再寫。 一:ArrayList不安全示例 使用ArrayList每次列印的集合數量可能會小於10000,而使用Vector每次都是10000 public class ListTes