你真的知道什么是線程安全嗎?
本文轉載自微信公眾號「老胡愛分享」,作者 hoohack。轉載本文請聯系老胡愛分享公眾號。
如果面試官問你,線程安全的類有哪些,究竟什么是線程安全?你怎么回答呢?我們整天說線程安全,但你真的知道什么是線程安全嗎?
什么是進程
從學術上理解,進程就是包含上下文切換的程序執行時間總和 = CPU加載上下文+CPU執行+CPU保存上下文。
另一個簡單的理解,進程就是程序的一次執行,比如看看一下這個圖,每一個運行中的程序就是一個獨立的進程,進程是相互獨立存在的。
什么是線程
線程就是CPU執行那一部分的一個個小段,線程是CPU的基本調度單位。
注:平時大家說“因為Redis是單線程的,所以它是原子性的”,根本原因是,因為線程是CPU的最小調度單元,CPU每次只能執行成功或者失敗才調度切換到下一個線程,所以Redis的操作都是原子的。
進程和線程都是一個時間段的描述,是CPU工作時間段的描述,不過是顆粒大小不同。
堆和棧
進程與線程中比較重要的內存區域有堆和棧。
堆是進程和線程共有的空間,分全局堆和局部堆。全局堆就是所有沒有分配的空間,局部堆就是用戶分配的空間。堆在操作系統對進程初始化的時候分配,運行過程中也可以向系統要額外的堆,但是用完了要還給操作系統,要不然就是內存泄漏。
在Java中,堆是Java虛擬機所管理的內存中最大的一塊,是所有線程共享的一塊內存區域,在虛擬機啟動時創建。堆所存在的內存區域的唯一目的就是存放對象實例,幾乎所有的對象實例以及數組都在這里分配內存。
棧是每個線程獨有的,保存其運行狀態和局部自動變量的。棧在線程開始的時候初始化,每個線程的棧互相獨立,因此,棧是線程安全的。操作系統在切換線程的時候會自動切換棧。棧空間不需要在高級語言里面顯式的分配和釋放。
進程和線程中的數據
程序幾乎都需要與數據打交道,讀取數據(命令行參數,文件),寫入數據(設置變量,寫入文件)。這些數據是保存在進程所管理的內存里。
為了保證數據的安全,比如一個進程中修改的數據不會影響到另一個進程的數據,每一個進程都會擁有操作系統分配給自己的內存空間,而不能訪問其他進程的數據,這一點是由操作系統保證的。
進程占有的資源:地址空間,全局變量,打開的文件,子進程,信號量,賬戶信息
線程占有的資源:棧,寄存器,狀態,程序計數器
進程是操作系統進行資源分配和調度的一個獨立單位,不會共享資源,通過進程間通信共享資源,而線程可以共享部分資源,獨自占有的資源不共享。
線程間共享的數據包括:
- 1、堆
- 2、進程代碼段
- 3、進程的公有數據
對于線程間共享的內存區域,如果進程中的A線程操作了數據,切換到B線程執行,修改了同樣的數據,回到A線程時,數據就不是A線程切換時候的樣子,這樣一來,數據就被污染了,我們就說這塊數據在多線程環境下是不安全的,即線程不安全的。
這就是線程安全這個概念產生的背景,筆者認為,談論線程安全性,一定需要先介紹操作系統中進程與線程操作內存的過程,否則,說一個對象是安全的還是不安全的就顯得有點突兀,而且相對于什么是安全的也不知道。
線程安全性
《Java并發編程實戰》給出的定義如下:
一個對象是否需要是線程安全的,取決于它是否被多個線程訪問。這只和對象在程序中是以何種方式被使用的有關,和對象本身具體是做什么的無關。
當多個線程訪問某個類時,這個類始終都能表現出正確的行為,那么就稱這個類是線程安全的。
線程安全的程序不一定是由線程安全的類組成,完全由線程安全類組成的程序也不一定是線程安全的。還需要一定的組合技巧才能保證線程安全。
要編寫線程安全的代碼,其核心在于要對對象狀態訪問操作進行管理,特別是對共享的(Shared)和可變的(Mutable)狀態的訪問,即數據的訪問,而數據是存儲在內存中,也就是說,線程安全的本質不是代碼在線程中的安全,而是線程中內存的安全。
至此,線程安全的概念介紹完畢,最后的最后,你知道有哪些方法可以保證線程安全嗎?
總結
分享一個學習方法,帶著問題去看書。有時候看一本書,從頭到尾看完確實非常枯燥無味,且很容易就放棄了,最近想到一個方法就是帶著問題去看,比如《Java并發編程實戰》,據說是Java并發編程的神書,但是很枯燥,而且中文版也難懂,看了好多次之后沒能進入狀態,后來就想著,能不能去網上看看一些面試題,看看這本書究竟能給我解答什么疑惑,懷著這樣的心情,就把前三章看完了。
帶著問題去看,目的性較強,更容易去理解,再通過自己的語言描述出來,印象就更深刻了。