JAVA線程及簡單同步實現的原理解析

JAVA線程及簡單同步實現的原理解析
線程
一、內容簡介:
  本文主要講述計算機中有關線程的相關內容,以及JAVA中關於線程的基礎知識點,爲以後的深入學習做鋪墊。如果你已經是高手了,那麼這篇文章並不適合你。

二、隨筆正文:
1、計算機系統組成
  計算機系統由計算機硬件系統和軟件系統組成。我們今天要說的線程和硬件系統中的cpu中央處理器,及軟件系統中的操作系統,進程等有比較緊密的聯繫。操作系統是軟件中比較特殊的存在,與硬件系統直接交互,其他程序(軟件)運行在操作系統之上。

2、cpu簡單說明
  硬件系統中特別重要的一項就是處理器CPU,與我們所說的線程有非常緊密的聯繫。cpu中有幾項參數,以及如何查看該信息,在下文逐一說明:

塊數:民用pc機,基本都是一塊物理cpu,每塊主板上只能裝一塊cpu。

核心數:也就是單塊物理cpu是由幾組處理芯片組,組成的。4核心 8核心等。

線程數:老款cpu都是單線程的,及一組芯片組只能運行一個線程。現款因特爾cpu大多支持超線程技術可支持多個邏輯線程。但是需要操作系統及相關編程語言的支持,JAVA相較C++在多線程方面能表現的更出色。

主頻:單位GHZ(hz赫茲 每秒的週期性變動重複次數)在計算機中即高低電平變化一次,可以產生兩個不同的電信號0、1。以我的CPU I5-4200M 2.5GHZ 舉例,及cpu可以每秒完成25億次震盪! 也就是說主頻越高理論上計算能力越強,處理計算機指令越快。但是並不代表計算機整體運算速度約高,這點通常滿足水桶效應,而cpu一直穩居長板地位。

緩存:cpu內置緩存,很小通常爲幾Mb至十幾Mb,和cpu交互更頻繁,速度也遠高於普通運行內存,提高cpu處理能力的有效手段。

查看cpu參數指令:

DOS命令
3、關係梳理
  操作系統,程序,進程,線程之間的關係梳理、

  程序:是計算機上的靜態代碼,指令文件集合,是靜態的存在。比如:QQ,LOL等

  進程:程序的執行實體(過程),持有及分配資源的主體。chrome.ext,QQ.exe等執行進程。

  線程:是進程中的勞動力,由進程創建,完成指定任務後結束。

  關係:

    操作系統 1 ——> n 程序 1 ——> n 進程 1 ——> n 線程

     平臺       集合     資源     幹活的

  普通進程創建線程去完成指定的計算機指令,這個時候需要調用系統資源如cpu進行運算,但是用戶線程並不能直接驅動硬件,而是通過操作系統去統一分配、控制硬件的使用。

4、進程、線程基本狀態

  五個基本狀態,創建和終止不說了。計算機中的線程創建之後會進入就緒狀態,當cpu爲此線程分配時間片時,線程由就緒轉爲執行狀態,開始幹活,當時間片結束時回到就緒狀態等待下次獲取時間片,循環直到任務完成,當任務完成時,線程終止(死亡)。若在執行過程中遇到耗時操作比如IO或者JAVA中的線程休眠等,會進入阻塞狀態,阻塞結束會進入就緒狀態繼續排隊等待被分配時間片。

5、JAVA中的線程
  java中提供了兩種方式去創建線程,繼承類和實現接口。由於java中單繼承機制的限制,大多數情況下使用實現Runnable接口的形式創建線程。

1 、繼承Thread類
繼承Thread類
2、實現Runnable接口
實現Runnable接口
3、線程的常用方法
複製代碼
1 start()
2 //啓動線程,調用run方法
3 sleep()
4 //線程休眠,進入阻塞狀態,讓出時間片,但不會讓出鎖
5 Thread.currentThread()
6 //獲取當前執行線程
7 wait/notify()
8 //僅能存在synchronized代碼塊中,wait線程休息,讓出時間片,進入等待狀態,notify()喚醒該線程;該方法存在重載
9 join()
10 //等待此線程執行完畢
11 setDaemon()
12 //設置守護線程,守護線程是服務線程,當用戶線程結束,守護線程自動結束
13 yield()
14 //主動讓出時間片給其他線程
15 interrupt()
16 //中斷線程,不推薦
17 get/setId()
18 //設置獲取線程id
19 get/setName()
20 //設置獲取線程名
21 get/setPriority()
22 //設置獲取線程優先級,理論上優先級越高,獲取時間片的概率越大,默認是5最高10最小1
複製代碼
4、JAVA中的線程狀態圖

5、線程練習
簡單模擬多線程購票業務

售票業務
  在不做任何控制的情況下,出錯的機率很小,我反覆測試幾次,結果基本都正確!分析原因:業務本身相對簡單,沒有耗時操作,每個時間片基本能保證線程將本次任務執行完畢!也就是將票數減一併打印內容。

  爲了模擬在售票前雙方的問詢階段及付款階段的等待,在售票前(後)加入Thread.sleep(ms) 模擬耗時操作。

加入線程休眠
  改動後系統出現bug同一張票被售出了多次,並且可能將票賣出負數。究其原因:線程之間的數據爭用問題,我們將引入JAVA內存模型進行分析(圖片來自網絡侵刪)

  首先我們需要知道幾個概念,如下:

  共享變量:主內存中存在被多個線程同時用到的變量,在多個線程中存在相應副本變量。

  可見性:線程對共享變量的值進行修改,能否被其他線程可見。

  變量訪問規則:   1、線程對共享變量的操作只能在自己工作內存中的副本。

            2、工作內存中的變量變化需要通過主內存傳遞。

6、實現同步
  實現同步即實現共享變量可見性,工作內存1 ——> 主內存 ——>工作內存2,在數據傳遞的兩個環節中出現問題都會對同步造成影響,從而影響執行結果。

  synchronized實現同步

  synchronized 可以實現指令的原子性,及共享變量的可見性。

  原子性:synchronize修飾的方法或者代碼塊會獲取互斥鎖,保證同一時間只能有一個對象訪問該方法或代碼塊,將其作爲一個整體,保證了原子性。

​   可見性:加鎖後,先清空工作內存,同步主內存中的共享變量。解鎖前,先將工作內存中的變量同步到主內存,再釋放鎖。

  所以結合上述例子,爲sellTickets()方法加鎖即可實現同步;或者對核心代碼片段加鎖;

複製代碼
private synchronized boolean sellTickets() { //… 省略中間代碼
}
  //或者如下實現
  
synchronized(this){

   //this指的是調用sellTickets()的對象

}
複製代碼
7、volatile關鍵字
  共享變量(類的成員變量、類的靜態成員變量)被volatile修飾之後,那麼就具備了兩層語義:

  原理:被volatile修飾的變量所生成彙編代碼時有lock前綴,生成"內存屏障"。

    1)保證了不同線程對這個變量進行操作時的可見性,即工作內存中的變量值在修改後會被立即同步到主內存中;

    2)並且使其他線程中的緩存無效,這樣當其他線程在訪問共享變量時就必須取主內存中獲取;

    3)禁止進行指令重排序;

  綜上所述,volatile可以保證可見性,但不能保證原子性;

  舉例分析:

複製代碼
volatile int number = 0 ;
number ++ ;
/*

操作可以解析成三步:  1.獲取number中的值 
                  2.計算加1操作 
                  3.number = 0 + 1; 
在一個時間片中,雖然volatile修飾的number一定會被立即同步到主內存中,但不能保證完整執行這三步,所以不能保證++操作的原子性 。

*/
複製代碼
三、總結說明:
  首先感謝各位能看到最後,對本文內容如果存在疑問,歡迎留言交流,若存在錯誤,也還望斧正!我將不定期對文章進行修改和調整,如發現錯誤一定及時改正以免誤人子弟!
原文地址https://www.cnblogs.com/lijizhi/p/10775748.html

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