深入理解java併發編程

深入理解Java併發編程(一):到底什麼是線程安全

本文是搞點事情!死磕Java併發編程。中的一篇試讀文章,更多文章,請參見:深入理解Java併發編程

什麼是線程安全

線程安全,維基百科中的解釋是:

線程安全是編程中的術語,指某個函數、函數庫在併發環境中被調用時,能夠正確地處理多個線程之間的共享變量,使程序功能正確完成。

我們把這個定義拆解一下,我們需要弄清楚這麼幾點: 1、併發 2、多線程 3、共享變量

併發

提到線程安全,必須要提及的一個詞那就是併發,如果沒有併發的話,那麼也就不存在線程安全問題了。

什麼是併發

併發(Concurrent),在操作系統中,是指一個時間段中有幾個程序都處於已啓動運行到運行完畢之間,且這幾個程序都是在同一個處理機上運行。

那麼,操作系統視如何實現這種併發的呢?

現在我們用到操作系統,無論是Windows、Linux還是MacOS等其實都是多用戶多任務分時操作系統。使用這些操作系統的用戶是可以“同時”幹多件事的。

但是實際上,對於單CPU的計算機來說,在CPU中,同一時間是隻能幹一件事兒的。爲了看起來像是“同時幹多件事”,分時操作系統是把CPU的時間劃分成長短基本相同的時間區間,即”時間片”,通過操作系統的管理,把這些時間片依次輪流地分配給各個用戶使用。

如果某個作業在時間片結束之前,整個任務還沒有完成,那麼該作業就被暫停下來,放棄CPU,等待下一輪循環再繼續做.此時CPU又分配給另一個作業去使用。

由於計算機的處理速度很快,只要時間片的間隔取得適當,那麼一個用戶作業從用完分配給它的一個時間片到獲得下一個CPU時間片,中間有所”停頓”,但用戶察覺不出來,好像整個系統全由它”獨佔”似的。

所以,在單CPU的計算機中,我們看起來“同時幹多件事”,其實是通過CPU時間片技術,併發完成的。

提到併發,還有另外一個詞容易和他混淆,那就是並行。

併發與並行之間的關係

並行(Parallel),當系統有一個以上CPU時,當一個CPU執行一個進程時,另一個CPU可以執行另一個進程,兩個進程互不搶佔CPU資源,可以同時進行,這種方式我們稱之爲並行(Parallel)。

Erlang 之父 Joe Armstrong 用一張比較形象的圖解釋了併發與並行的區別:

concurrent vs parallel

併發是兩個隊伍交替使用一臺咖啡機。並行是兩個隊伍同時使用兩臺咖啡機。

映射到計算機系統中,上圖中的咖啡機就是CPU,兩個隊伍指的就是兩個進程。

多線程

進程和線程

理解了併發和並行之間的關係和區別後,我們再回到前面介紹的多任務分時操作系統,看看CPU是如何進行進程調度的。

爲了看起來像是“同時幹多件事”,分時操作系統是把CPU的時間劃分成長短基本相同的”時間片”,通過操作系統的管理,把這些時間片依次輪流地分配給各個用戶的各個任務使用。

在多任務處理系統中,CPU需要處理所有程序的操作,當用戶來回切換它們時,需要記錄這些程序執行到哪裏。在操作系統中,CPU切換到另一個進程需要保存當前進程的狀態並恢復另一個進程的狀態:當前運行任務轉爲就緒(或者掛起、刪除)狀態,另一個被選定的就緒任務成爲當前任務。上下文切換就是這樣一個過程,他允許CPU記錄並恢復各種正在運行程序的狀態,使它能夠完成切換操作。

在上下文切換過程中,CPU會停止處理當前運行的程序,並保存當前程序運行的具體位置以便之後繼續運行。從這個角度來看,上下文切換有點像我們同時閱讀幾本書,在來回切換書本的同時我們需要記住每本書當前讀到的頁碼。在程序中,上下文切換過程中的“頁碼”信息是保存在進程控制塊(PCB)中的。PCB還經常被稱作“切換幀”(switchframe)。“頁碼”信息會一直保存到CPU的內存中,直到他們被再次使用。

對於操作系統來說,一個任務就是一個進程(Process),比如打開一個瀏覽器就是啓動一個瀏覽器進程,打開一個記事本就啓動了一個記事本進程,打開兩個記事本就啓動了兩個記事本進程,打開一個Word就啓動了一個Word進程。

而在多個進程之間切換的時候,需要進行上下文切換。但是上下文切換勢必會耗費一些資源。於是人們考慮,能不能在一個進程中增加一些“子任務”,這樣減少上下文切換的成本。比如我們使用Word的時候,它可以同時進行打字、拼寫檢查、字數統計等,這些子任務之間共用同一個進程資源,但是他們之間的切換不需要進行上下文切換。

在一個進程內部,要同時幹多件事,就需要同時運行多個“子任務”,我們把進程內的這些“子任務”稱爲線程(Thread)。

隨着時間的慢慢發展,人們進一步的切分了進程和線程之間的職責。把進程當做資源分配的基本單元,把線程當做執行的基本單元,同一個進程的多個線程之間共享資源

拿我們比較熟悉的Java語言來說,Java程序是運行在JVM上面的,每一個JVM其實就是一個進程。所有的資源分配都是基於JVM進程來的。而在這個JVM進程中,又可以創建出很多線程,多個線程之間共享JVM資源,並且多個線程可以併發執行。

共享變量

所謂共享變量,指的是多個線程都可以操作的變量。

前面我們提到過,進程視分配資源的基本單位,線程是執行的基本單位。所以,多個線程之間是可以共享一部分進程中的數據的。在JVM中,Java堆和方法區的區域是多個線程共享的數據區域。也就是說,多個線程可以操作保存在堆或者方法區中的同一個數據。那麼,換句話說,保存在堆和方法區中的變量就是Java中的共享變量。

那麼,Java中哪些變量是存放在堆中,哪些變量是存放在方法區中,又有哪些變量是存放在棧中的呢?

類變量、成員變量和局部變量

Java中共有三種變量,分別是類變量、成員變量和局部變量。他們分別存放在JVM的方法區、堆內存和棧內存中。

/**
 * @author Hollis
 */
public class Variables {
</span></span><span class="com"><span class="com">/**
 * 類變量
 */</span></span><span class="pln"><span class="pln">
</span></span><span class="kwd"><span class="kwd">private</span></span><span class="pln"><span class="pln"> </span></span><span class="kwd"><span class="kwd">static</span></span><span class="pln"><span class="pln"> </span></span><span class="kwd"><span class="kwd">int</span></span><span class="pln"><span class="pln"> a</span></span><span class="pun"><span class="pun">;</span></span><span class="pln"><span class="pln">

</span></span><span class="com"><span class="com">/**
 * 成員變量
 */</span></span><span class="pln"><span class="pln">
</span></span><span class="kwd"><span class="kwd">private</span></span><span class="pln"><span class="pln"> </span></span><span class="kwd"><span class="kwd">int</span></span><span class="pln"><span class="pln"> b</span></span><span class="pun"><span class="pun">;</span></span><span class="pln"><span class="pln">

</span></span><span class="com"><span class="com">/**
 * 局部變量
 * @param c
 */</span></span><span class="pln"><span class="pln">
</span></span><span class="kwd"><span class="kwd">public</span></span><span class="pln"><span class="pln"> </span></span><span class="kwd"><span class="kwd">void</span></span><span class="pln"><span class="pln"> test</span></span><span class="pun"><span class="pun">(</span></span><span class="kwd"><span class="kwd">int</span></span><span class="pln"><span class="pln"> c</span></span><span class="pun"><span class="pun">){</span></span><span class="pln"><span class="pln">
    </span></span><span class="kwd"><span class="kwd">int</span></span><span class="pln"><span class="pln"> d</span></span><span class="pun"><span class="pun">;</span></span><span class="pln"><span class="pln">
</span></span><span class="pun"><span class="pun">}</span></span><span class="pln"><span class="pln">

}

上面定義的三個變量中,變量a就是類變量,變量b就是成員變量,而變量c和d是局部變量。

所以,變量a和b是共享變量,變量c和d是非共享變量。所以如果遇到多線程場景,對於變量a和b的操作是需要考慮線程安全的,而對於線程c和d的操作是不需要考慮線程安全的。

小結

在瞭解了一些基礎知識以後,我們再來回過頭看看線程安全的定義:

線程安全是編程中的術語,指某個函數、函數庫在併發環境中被調用時,能夠正確地處理多個線程之間的共享變量,使程序功能正確完成。

現在我們知道了什麼是併發環境,什麼是多個線程以及什麼是共享變量。那麼只要我們在編寫多線程的代碼的時候注意一下,保證程序功能可以正確的執行就行了。

那麼問題來了,定義中說線程安全能夠正確地處理多個線程之間的共享變量,使程序功能正確完成

多線程場景中存在哪些問題會導致無法正確的處理共享變量? 多線程場景中存在哪些問題會導致程序無法正確完成? 如何解決多線程場景中影響『正確』的這些問題? 解決這些問題的各個手段的實現原理又是什麼?

以上問題會在後續文章中介紹。

更多 (0)
	<div class="article-tags">標籤:<a href="https://www.hollischuang.com/archives/tag/%e6%b7%b1%e5%85%a5%e7%90%86%e8%a7%a3java%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b" rel="tag">深入理解Java併發編程</a></div>
	
	
	            <nav class="article-nav">
            <span class="article-nav-prev">上一篇<br><a href="https://www.hollischuang.com/archives/3054" rel="prev">深入理解Java併發編程(二):進一步理解Java中的線程(上)</a></span>
            <span class="article-nav-next">下一篇<br><a href="https://www.hollischuang.com/archives/3076" rel="next">世界上最難學的編程語言,Java只排第三,第一你絕對想不到!</a></span>
        </nav>
    
			<div class="relates"><div class="title"><h3>相關推薦</h3></div><ul><li><a href="https://www.hollischuang.com/archives/3838">密碼保護:深入理解Java併發編程(九):到底什麼是Java內存模型?</a></li><li><a href="https://www.hollischuang.com/archives/3526">密碼保護:深入理解Java併發編程(八):到底什麼是計算機內存模型?</a></li><li><a href="https://www.hollischuang.com/archives/3309">密碼保護:深入理解Java併發編程(七):計算機硬件升級帶來的問題</a></li><li><a href="https://www.hollischuang.com/archives/3175">密碼保護:深入理解Java併發編程(五):Java線程池的實現原理</a></li><li><a href="https://www.hollischuang.com/archives/3179">密碼保護:深入理解Java併發編程(六):使用線程池的正確姿勢</a></li><li><a href="https://www.hollischuang.com/archives/3132">密碼保護:深入理解Java併發編程(四):創建線程的多種方式</a></li><li><a href="https://www.hollischuang.com/archives/3121">密碼保護:深入理解Java併發編程(三):進一步理解Java中的線程(下)</a></li><li><a href="https://www.hollischuang.com/archives/3054">密碼保護:深入理解Java併發編程(二):進一步理解Java中的線程(上)</a></li></ul></div>				<div class="title" id="comments">
<h3>評論 <b>2</b></h3>
<form action="https://www.hollischuang.com/wp-comments-post.php" method="post" id="commentform">
	<div class="comt">
		<div class="comt-title">
			<img data-src="https://secure.gravatar.com/avatar/?s=100&amp;d=mm" class="avatar avatar-100" height="50" width="50" src="https://www.hollischuang.com/wp-content/themes/dux/img/avatar-default.png">				<p><a id="cancel-comment-reply-link" href="javascript:;">取消</a></p>
		</div>
		<div class="comt-box">
			<textarea placeholder="期待你的評論~" class="input-block-level comt-area" name="comment" id="comment" cols="100%" rows="3" tabindex="1" onkeydown="if(event.ctrlKey&amp;&amp;event.keyCode==13){document.getElementById('submit').click();return false};"></textarea>
			<div class="comt-ctrl">
				<div class="comt-tips"><input type="hidden" name="comment_post_ID" value="3060" id="comment_post_ID">

評論提交中...
#
提交評論
											<div class="comt-comterinfo" id="comment-author-info">
					<ul>
						<li class="form-inline"><label class="hide" for="author">暱稱</label><input class="ipt" type="text" name="author" id="author" value="" tabindex="2" placeholder="暱稱"><span class="text-muted">暱稱 (必填)</span></li>
						<li class="form-inline"><label class="hide" for="email">郵箱</label><input class="ipt" type="text" name="email" id="email" value="" tabindex="3" placeholder="郵箱"><span class="text-muted">郵箱 (必填)</span></li>
						<li class="form-inline"><label class="hide" for="url">網址</label><input class="ipt" type="text" name="url" id="url" value="" tabindex="4" placeholder="網址"><span class="text-muted">網址</span></li>
					</ul>
				</div>
								</div>

<input type="hidden" id="ak_js" name="ak_js" value="1569323551331"></form>
</div>
  1. #1

    你好,探討一下,在小節“類變量、成員變量和局部變量”中,寫道“變量a和b是共享變量,變量c和d是非共享變量”。
    但如果c和d是非基本類型(對象),且聲明爲final,即可在此方法域內聲明的新線程進行共享,此時c和d也是線程共享的變量了。所以我理解局部變量,不一定是線程安全的吧?

    hh6plus7個月前 (02-23)回覆
  2. #2

    說的好

    測試4周前 (08-29)回覆
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章