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































