Java多線程和併發編程實踐的學習心得----基礎篇3

Java多線程和併發編程實踐的學習心得----基礎篇3


三 線程的同步與併發


併發:併發就是多個線程同時去完成一點複雜的事情,在實際應用中,經常用到線程的併發,多條線程做完成一件事情和一條線程去完成一件事情,那是無法同言而語的。 同時併發編程,可以充分利用服務器資源,提高服務吞吐量、降低響應時間(爬蟲、WebServer、日誌分析……),以及在一些分佈式系統(資源的爭用、可伸縮性)的服務器編程中到處都要用的併發編程。


同步:當線程併發的時候,隨之而來的也帶來了一些問題,如果多條線程去同時操作共享而用的數據那將是會使共享的數據很容易就出現錯誤。在JAVA中提供同防止多條線程同時共享數據的方式是:synchronized,volatile很好的利用這兩個關鍵字就能防止併發而帶來的問題,  同時在JAVA1.5之後也提供了更多好用的類來解決這個問題 。

線程池:當有許多請求需要去處理的時候,如果只是單獨的一個人去處理,可想而知那會讓後面在排隊的人等多久,這樣就需要線程池,有請求過來了就到線程池裏 面取出一條線程去處理它,處理完成就把它收回到線程池裏面,然而自己實現 一個功能強大的線程池也並非易事,在java1.5之後專門提供了線程池的類庫。下圖是線程池的模型:





1 常見術語解釋:


可見性:在沒有使用同步機制的情況下,一個線程對某個共享對象的修改,並不會立即被其它的線程讀取到。


在Java內存抽象模型中,如果多個線程對一個變量進行操作,但是這多個線程有可能被分配到多個處理器中運行,那麼編譯器會對代碼進行優化,當線程要處理該變量時,處理器會將變量從主內存複製一份分別存儲在自己的片上存儲器中,等到進行完操作後,再賦值回主存。這樣做的好處是提高了運行的速度;

上上一節,基礎篇1中已經看到過這個圖了,這個是Java虛擬機內存模型的抽象模型:

所以,從上圖可以看出,如果線程A與線程B分別被安排在了不同的處理器上面,那麼t1與t2對於變量A的修改時相互不可見,如果A給x賦值,然後B又賦新值,那麼B的操作就將A的操作覆蓋掉了,這樣會產生不可預料的結果。

同時,在有共享變量的多線程程序中,也會發生問題,這裏就涉及到共享對象的可見性了,也就是在沒有使用同步機制的情況下,一個線程對某個共享對象的修改,並不會立即被其它的線程讀取到。


JMM中的happened-before規則:

  • 如果線程A寫入了volatile變量v,接着線程B讀取了v,那麼,線程A寫入v及之前的寫操作都對線程B可見。
  • 線程中上一個動作及之前的所有寫操作在該線程執行下一個動作時對該線程可見
  • 如果線程A解鎖了,接着線程B鎖定了a,那麼,線程A解鎖a之前的寫操作都對線程2可見
  • happends-before有傳遞性

 


原子性:原子的意思代表着——“不可分”


顧名思義:原子性就是不被線程調度器中斷的操作。
如賦值或者return。比如"a = 1;"和 "return a;"這樣的操作都具有原子性

但是類似"a += b"後者 “return  i++ ”這樣的操作不具有原子性。

在JVM中"a += b"可能要經過這樣三個步驟:(1)取出a和b  (2)計算a+b   (3)將計算結果寫入內存

如果有兩個線程t1,t2在進行這樣的操作。t1在第二步做完之後還沒來得及把數據寫回內存就被線程調度器中斷了,於是t2開始執行,t2執行完畢後t1又把沒有完成的第三步做完。這個時候就出現了錯誤,相當於t2的計算結果被無視掉了。 等等,這些非原子性操作在多線程的運行程序中非常的多見。 所以,在多線程的編程中,一定要注意同步加鎖等操作。





有序性:程序執行順序

首先要明白,代碼順序並不一定等於,程序執行的順序。
如在單線程的例子中:一般情況下,代碼順序等於程序執行的順序。但如果沒有數據依賴的話是允許重排序,就是不一定安裝代碼的順序執行,由於沒有數據依賴,對程序的結果不會產生任何影響。
如在多線程中可就不一樣了,多線程中3個顯著特點是:
  • 整體無序:i++
  • 線程內重排序
  • 線程間看到的順序也不一致(可見性導致)

看看理想的情況下:Java虛擬機中的順序一致性。




但是在真實情況是這樣的,在正確同步的前期下:






線程安全:


線程安全就是多線程訪問時,採用了加鎖等機制,當一個線程訪問該類的某個數據時,進行保護,其他線程不能進行訪問直到該線程讀取完,其他線程纔可使用。不會出現數據不一致或者數據污染。

線程不安全就是不提供數據訪問保護,有可能出現多個線程先後更改數據造成所得到的數據是髒數據

或者說:一個類或者程序所提供的接口對於線程來說是原子操作或者多個線程之間的切換不會導致該接口的執行結果存在二義性,也
就是說我們不用考慮同步的問題。

線程安全問題都是由全局變量及靜態變量引起的。
若每個線程中對全局變量、靜態變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個
線程同時執行寫操作,一般都需要考慮線程同步,否則的話就可能影響線程安全。

2  鎖的原理


任何對象都有一個monitor(監視器/管程)與之關聯,當且一個monitor被持有後,它將處於鎖定狀態。Java中

就是synchronized 和volatile關鍵字了。下面來比較下他們的區別:


•synchronized可以做到:–可重入、互斥性、可見性

•synchronized不能做到:
–等待超時、可中斷、公平性

•volatile可以做到:
原子性、可見性
•volatile不能做到:複合操作的原子性  volatile引用指向對象屬性/數組元素的可見性
•volatile提供:與鎖一致的語義,並且比鎖的開銷小


下一節看看java在java.util.concurrent.*類中的關於對線程的知識點:


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章