多线程学习笔记一之内置锁
目錄
- 多線程的概念
- 創(chuàng)建線程
- 線程狀態(tài)
- synchronized同步鎖
- volatile
多線程的概念
什么是多線程
??操作系統(tǒng)在運(yùn)行程序時(shí),就會(huì)為其創(chuàng)建一個(gè)進(jìn)程,我們可以把進(jìn)程理解為“運(yùn)行中的程序”,它擁有獨(dú)立的地址空間,包括文本區(qū)域(text region)、數(shù)據(jù)區(qū)域(data region)和堆棧(stack region);而一個(gè)進(jìn)程又能夠創(chuàng)建多個(gè)線程,這些線程擁有各自的計(jì)數(shù)器、堆棧和局部變量等屬性,并且能夠訪問共享的內(nèi)存變量。
多線程的好處
??Java作為在服務(wù)端廣泛使用的語言,內(nèi)置了對多線程的支持。相對于我們熟悉的串行執(zhí)行的單線程程序,多線程程序可能因?yàn)槎嗑€程競爭發(fā)生意想不到的錯(cuò)誤,所以需要我們對多線程有一個(gè)深入了解。通過使用多線程,我們可以把計(jì)算任務(wù)分配給多個(gè)處理器,提高效率;也可以提高程序的響應(yīng)時(shí)間,比如最近我在做一個(gè)項(xiàng)目設(shè)計(jì)到TCP傳輸消息給服務(wù)器,服務(wù)器端通過校驗(yàn)解析后將信息推送給客戶端并保存到數(shù)據(jù)庫,然后把響應(yīng)消息通過TCP連接返回,如果使用單線程必須要等到消息推送和保存完畢之后才能夠返回響應(yīng)信息,響應(yīng)時(shí)間很長,通過多線程在校驗(yàn)完消息后就可以直接返回響應(yīng)消息,通過另外一個(gè)線程進(jìn)行推送保存,這樣就縮短了響應(yīng)時(shí)間。
創(chuàng)建線程
??有兩種創(chuàng)建線程的方式:實(shí)現(xiàn)接口以及繼承Thread類。
實(shí)現(xiàn)接口
通過實(shí)現(xiàn)Runnable接口重寫run方法。
public class StartThread {public static void main(String[] args) {new Thread(new ToyThread()).start();}private static class ToyThread implements Runnable {@Overridepublic void run() {System.out.println("this is ToyThread");}} }繼承Thread類
public class StartThread {public static void main(String[] args) {new ToyThread().start();}private static class ToyThread extends Thread {@Overridepublic void run() {System.out.println("this is ToyThread");}} }不管是實(shí)現(xiàn)Runnable接口還是繼承Thread類,在新建了線程對象了通過調(diào)用start方法,線程處于就緒狀態(tài)等待操作系統(tǒng)調(diào)用。
線程狀態(tài)
| NEW | 初始狀態(tài),線程對象被構(gòu)建,但還沒有調(diào)用start()方法 |
| RUNNABLE | 運(yùn)行狀態(tài),線程可能正在等待操作系統(tǒng)調(diào)用,也可能正在運(yùn)行 |
| BLOCKED | 阻塞狀態(tài),表示線程等待獲取鎖 |
| WAITING | 等待狀態(tài),表示線程進(jìn)入等待狀態(tài),進(jìn)入該狀態(tài)表示當(dāng)前線程需要其他線程做出特定動(dòng)作(通知或中斷) |
| TIME_WAITING | 超時(shí)等待狀態(tài),該狀態(tài)不同于WAITING,可以在等待指定時(shí)間后自行返回的 |
| TERMINATED | 終止?fàn)顟B(tài),表示當(dāng)前線程已經(jīng)執(zhí)行完畢,或者產(chǎn)生異常結(jié)束 |
線程狀態(tài)轉(zhuǎn)換:
synchronized同步鎖
??為了防止多個(gè)線程對共享資源進(jìn)行訪問讀寫時(shí)引發(fā)的線程安全問題(線程安全:當(dāng)多個(gè)線程訪問一個(gè)對象時(shí),如果不用考慮這些線程在運(yùn)行時(shí)環(huán)境下的調(diào)度和交替執(zhí)行,也不需要進(jìn)行額外的同步,或者在調(diào)用方法進(jìn)行任何其他的協(xié)調(diào)操作,調(diào)用這個(gè)對象的行為都可以獲得正確的結(jié)果,那這個(gè)對象是線程安全的),如果不是線程安全的,我們就需要對共享變量進(jìn)行互斥同步,可以使用基于JVM實(shí)現(xiàn)的synchronized同步鎖。
??synchronized是Java關(guān)鍵字,是基于JVM實(shí)現(xiàn)的同步鎖,能夠?qū)崿F(xiàn)對共享資源的互斥同步。synchronized實(shí)現(xiàn)同步的基礎(chǔ):Java中的每一個(gè)對象都可以作為鎖,表現(xiàn)在以下三種形式:
- 對于普通的以synchronized修飾的同步方法,鎖是當(dāng)前的實(shí)例對象。
- 對于synchronized修飾的靜態(tài)方法,鎖是當(dāng)前類的Class對象。
- 對于同步代碼塊,鎖是synchronized括號(hào)配置的對象。
當(dāng)一個(gè)線程試圖訪問同步代碼塊時(shí),首先必須要得到鎖,退出或拋出異常時(shí)必須要釋放鎖;Java的內(nèi)置鎖是一種互斥鎖,如果有其他線程先于當(dāng)前線程獲得鎖,那么當(dāng)前線程就處于Blocked狀態(tài),等待其他線程把鎖釋放了才能訪問同步代碼塊。為什么說synchronized通過對象獲取鎖?下面將介紹在HotSpot虛擬機(jī)中對象的對象頭。
對象頭
??HotSpot虛擬機(jī)中,對象在內(nèi)存中存儲(chǔ)的布局可以分為三塊區(qū)域:對象頭(Header)、實(shí)例數(shù)據(jù)(Instance Data)和對齊填充(Padding),synchronized用的鎖存在Java對象頭,如果對象是數(shù)組類型,則虛擬機(jī)用3個(gè)字寬(Word)存儲(chǔ)對象頭,如果是非數(shù)組對象,則用2字寬存儲(chǔ)對象頭,在32位虛擬機(jī)中,一字寬等于4字節(jié),即32bit,我們重點(diǎn)關(guān)注對象頭與同步鎖密切相關(guān)的Mark Word:
| 32/64bit | Mark Word | 存儲(chǔ)對象的hashCode或鎖信息 |
| 32/64bit | Class Metadata Address | 存儲(chǔ)到對象類型數(shù)據(jù)的指針,虛擬機(jī)通過這個(gè)指針來確定這個(gè)對象是哪個(gè)類的實(shí)例。 |
| 32/64bit | Array | 數(shù)組的長度(如果當(dāng)前對象是數(shù)組) |
32位JVM的Mark Word的默認(rèn)存儲(chǔ)結(jié)構(gòu):
為了盡可能多的存儲(chǔ)信息,在運(yùn)行期間,Mark Word里存儲(chǔ)的數(shù)據(jù)會(huì)隨著鎖標(biāo)志位的變化而變化。從圖中可以看到,鎖一共有四種狀態(tài),從級(jí)別由低到高一次是:無鎖態(tài)、偏向鎖狀態(tài)、輕量級(jí)鎖狀態(tài)和重量級(jí)鎖狀態(tài),這幾種狀態(tài)隨著競爭狀況逐漸升級(jí)。這是JDK1.6為了減少獲得鎖和釋放鎖帶來的性能消耗對同步鎖進(jìn)行了優(yōu)化。
??當(dāng)一個(gè)線程訪問同步塊并獲取鎖時(shí),會(huì)在對象頭和棧幀中的鎖記錄里存儲(chǔ)鎖偏向的線程ID,以后該線程在進(jìn)入和退出同步塊時(shí)不需要進(jìn)行CAS操作來枷鎖和解鎖,只需要測試一下對象頭的Mark Word里是否存儲(chǔ)著指向當(dāng)前線程的偏向鎖,如果測試成功,表示線程已經(jīng)獲得鎖,如果測試失敗,則需要再測試下Mark Word中偏向鎖的標(biāo)識(shí)是否設(shè)置成1:如果沒有設(shè)置,則使用CAS競爭鎖;如果設(shè)置了,則嘗試使用CAS將對象頭的偏向鎖指向當(dāng)前線程。
volatile
??volatile是Java關(guān)鍵字,它在多線程程序中保證了共享變量的“可見性”,即當(dāng)一個(gè)線程修改了共享變量,另外一個(gè)線程能讀到這個(gè)修改的值。Java并發(fā)模型如何實(shí)現(xiàn)共享變量的可見性呢?下面將介紹Java內(nèi)存模型:
Java內(nèi)存模型JMM
??Java線程之間的通信由Java內(nèi)存模型(本文簡稱為JMM)控制,JMM決定一個(gè)線程對共享變量的寫入何時(shí)對另一個(gè)線程可見。從抽象的角度來看,JMM定義了線程和主內(nèi)存之間的抽象關(guān)系:線程之間的共享變量存儲(chǔ)在主內(nèi)存(Main Memory)中,每個(gè)線程都有一個(gè)私有的本地內(nèi)存(Local Memory),本地內(nèi)存中存儲(chǔ)了該線程以讀/寫共享變量的副本。本地內(nèi)存是JMM的一個(gè)抽象概念,并不真實(shí)存在。它涵蓋了緩存、寫緩沖區(qū)、寄存器以及其他的硬件和編譯器優(yōu)化。Java內(nèi)存模型的抽象示意如圖所示:
??線程A和線程B都有主內(nèi)存中共享變量的副本,線程A在執(zhí)行過程中修改了共享變量副本的值,當(dāng)線程A需要把修改后的值讓其他線程也收到,就需要把修改后的值刷新到主內(nèi)存,隨后線程B到主內(nèi)存讀取修改后的值,這是線程B讀取的共享變量的值就與線程A一致了。
??那么將貢獻(xiàn)變量副本的值刷新到主內(nèi)存以及強(qiáng)制到主內(nèi)存讀取刷新后的共享變量的值就是volatile的功效了。volatile的功能實(shí)現(xiàn)與內(nèi)存屏障(memory barrier,是一個(gè)CPU指令能確保一些特定操作執(zhí)行的順序以及影響一些數(shù)據(jù)的可見性)如果字段被volatile修飾,Java內(nèi)存模型將在寫操作后插入一個(gè)寫屏障指令,在讀操作前插入一個(gè)讀屏障指令。這意味著如果你對一個(gè)volatile字段進(jìn)行寫操作,你必須知道:1、一旦你完成寫入,任何訪問這個(gè)字段的線程將會(huì)得到最新的值。2、在你寫入前,會(huì)保證所有之前發(fā)生的事已經(jīng)發(fā)生,并且任何更新過的數(shù)據(jù)值也是可見的,因?yàn)閮?nèi)存屏障會(huì)把之前的寫入值都刷新到緩存。
總體而言,volatile具有下列特性:
- 可見性:對一個(gè)volatile變量的讀,總是能看到(任意線程)對這個(gè)volatile變量最后的寫入。
- 原子性:對任意單個(gè)volatile變量的讀/寫具有原子性,但對于類似volatile++這種符合操作不具有原子性。
參考資料:
Java并發(fā)編程的藝術(shù)
轉(zhuǎn)載于:https://www.cnblogs.com/rain4j/p/10039124.html
總結(jié)
以上是生活随笔為你收集整理的多线程学习笔记一之内置锁的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 道路交通实时流量监控预测系统(大讲台)
- 下一篇: 洛谷 P2486 [SDOI2011]染