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