卷妹帶你回顧Java基礎(一)每日更新Day9
👩💻部落格首頁:京與舊鋪的部落格首頁
✨歡迎關注🖱點贊🎀收藏⭐留言✒
🔮本文由京與舊鋪原創,
😘系列專欄:java學習
👕參考網站:牛客網
💻首發時間:🎞2022年8月15日🎠
🎨你做三四月的事,八九月就會有答案,一起加油吧
🀄如果覺得部落客的文章還不錯的話,請三連支援一下部落客哦
🎧最後的話,作者是一個新人,在很多方面還做的不好,歡迎大佬指正,一起學習哦,沖沖沖
💬推薦一款模拟面試、刷題神器👉點選進入網站
🛒導航小助手🎪
文章目錄
- 卷妹帶你回顧Java基礎(一)每日更新Day9
- 🛒導航小助手🎪
- 4.11 如何實作子線程先執行,主線程再執行?
- 4.12 阻塞線程的方式有哪些?
- 4.13 說一說synchronized與Lock的差別
- 4.15 synchronized可以修飾靜态方法和靜态代碼塊嗎?
- 4.17 如果不使用synchronized和Lock,如何保證線程安全?
- 4.18 說一說Java中樂觀鎖和悲觀鎖的差別
- 4.21 如何實作互斥鎖(mutex)?
- 4.23 說說你對讀寫鎖的了解
- 4.24 volatile關鍵字有什麼用?
- 4.25 談談volatile的實作原理
4.11 如何實作子線程先執行,主線程再執行?
參考答案
啟動子線程後,立即調用該線程的join()方法,則主線程必須等待子線程執行完成後再執行。
擴充閱讀
Thread類提供了讓一個線程等待另一個線程完成的方法——join()方法。當在某個程式執行流中調用其他線程的join()方法時,調用線程将被阻塞,直到被join()方法加入的join線程執行完為止。
join()方法通常由使用線程的程式調用,以将大問題劃分成許多小問題,每個小問題配置設定一個線程。當所有的小問題都得到處理後,再調用主線程來進一步操作。
4.12 阻塞線程的方式有哪些?
參考答案
當發生如下情況時,線程将會進入阻塞狀态:
- 線程調用sleep()方法主動放棄所占用的處理器資源;
- 線程調用了一個阻塞式IO方法,在該方法傳回之前,該線程被阻塞;
- 線程試圖獲得一個同步螢幕,但該同步螢幕正被其他線程所持有;
- 線程在等待某個通知(notify);
- 程式調用了線程的suspend()方法将該線程挂起,但這個方法容易導緻死鎖,是以應該盡量避免使用該方法。
4.13 說一說synchronized與Lock的差別
參考答案
- synchronized是Java關鍵字,在JVM層面實作加鎖和解鎖;Lock是一個接口,在代碼層面實作加鎖和解鎖。
- synchronized可以用在代碼塊上、方法上;Lock隻能寫在代碼裡。
- synchronized在代碼執行完或出現異常時自動釋放鎖;Lock不會自動釋放鎖,需要在finally中顯示釋放鎖。
- synchronized會導緻線程拿不到鎖一直等待;Lock可以設定擷取鎖失敗的逾時時間。
- synchronized無法得知是否擷取鎖成功;Lock則可以通過tryLock得知加鎖是否成功。
- synchronized鎖可重入、不可中斷、非公平;Lock鎖可重入、可中斷、可公平/不公平,并可以細分讀寫鎖以提高效率。
4.15 synchronized可以修飾靜态方法和靜态代碼塊嗎?
參考答案
synchronized可以修飾靜态方法,但不能修飾靜态代碼塊。
當修飾靜态方法時,螢幕鎖(monitor)便是對象的Class執行個體,因為Class資料存在于永久代,是以靜态方法鎖相當于該類的一個全局鎖。
4.17 如果不使用synchronized和Lock,如何保證線程安全?
參考答案
-
volatile
volatile關鍵字為域變量的通路提供了一種免鎖機制,使用volatile修飾域相當于告訴虛拟機該域可能會被其他線程更新,是以每次使用該域就要重新計算,而不是使用寄存器中的值。需要注意的是,volatile不會提供任何原子操作,它也不能用來修飾final類型的變量。
-
原子變量
在java的util.concurrent.atomic包中提供了建立了原子類型變量的工具類,使用該類可以簡化線程同步。例如AtomicInteger 表可以用原子方式更新int的值,可用在應用程式中(如以原子方式增加的計數器),但不能用于替換Integer。可擴充Number,允許那些處理機遇數字類的工具和實用工具進行統一通路。
-
本地存儲
可以通過ThreadLocal類來實作線程本地存儲的功能。每一個線程的Thread對象中都有一個ThreadLocalMap對象,這個對象存儲了一組以ThreadLocal.threadLocalHashCode為鍵,以本地線程變量為值的K-V值對,ThreadLocal對象就是目前線程的ThreadLocalMap的通路入口,每一個ThreadLocal對象都包含了一個獨一無二的threadLocalHashCode值,使用這個值就可以線上程K-V值對中找回對應的本地線程變量。
-
不可變的
隻要一個不可變的對象被正确地建構出來,那其外部的可見狀态永遠都不會改變,永遠都不會看到它在多個線程之中處于不一緻的狀态,“不可變”帶來的安全性是最直接、最純粹的。Java語言中,如果多線程共享的資料是一個基本資料類型,那麼隻要在定義時使用final關鍵字修飾它就可以保證它是不可變的。如果共享資料是一個對象,由于Java語言目前暫時還沒有提供值類型的支援,那就需要對象自行保證其行為不會對其狀态産生任何影響才行。String類是一個典型的不可變類,可以參考它設計一個不可變類。
4.18 說一說Java中樂觀鎖和悲觀鎖的差別
參考答案
悲觀鎖:總是假設最壞的情況,每次去拿資料的時候都認為别人會修改,是以每次在拿資料的時候都會上鎖,這樣别人想拿這個資料就會阻塞直到它拿到鎖。Java中悲觀鎖是通過synchronized關鍵字或Lock接口來實作的。
樂觀鎖:顧名思義,就是很樂觀,每次去拿資料的時候都認為别人不會修改,是以不會上鎖,但是在更新的時候會判斷一下在此期間别人有沒有去更新這個資料。樂觀鎖适用于多讀的應用類型,這樣可以提高吞吐量。在JDK1.5 中新增 java.util.concurrent (J.U.C)就是建立在CAS之上的。相對于對于 synchronized 這種阻塞算法,CAS是非阻塞算法的一種常見實作。是以J.U.C在性能上有了很大的提升。
4.21 如何實作互斥鎖(mutex)?
參考答案
在Java裡面,最基本的互斥同步手段就是synchronized關鍵字,這是一種塊結構(Block Structured)的同步文法。synchronized關鍵字經過Javac編譯之後,會在同步塊的前後分别形成monitorenter和monitorexit這兩個位元組碼指令。這兩個位元組碼指令都需要一個reference類型的參數來指明要鎖定和解鎖的對象。如果Java源碼中的synchronized明确指定了對象參數,那就以這個對象的引用作為reference。如果沒有明确指定,那将根據synchronized修飾的方法類型(如執行個體方法或類方法),來決定是取代碼所在的對象執行個體還是取類型對應的Class對象來作為線程要持有的鎖。
自JDK 5起,Java類庫中新提供了java.util.concurrent包(J.U.C包),其中的java.util.concurrent.locks.Lock接口便成了Java的另一種全新的互斥同步手段。基于Lock接口,使用者能夠以非塊結構(Non-Block Structured)來實作互斥同步,進而擺脫了語言特性的束縛,改為在類庫層面去實作同步,這也為日後擴充出不同排程算法、不同特征、不同性能、不同語義的各種鎖提供了廣闊的空間。
4.23 說說你對讀寫鎖的了解
參考答案
與傳統鎖不同的是讀寫鎖的規則是可以共享讀,但隻能一個寫,總結起來為:讀讀不互斥、讀寫互斥、寫寫互斥,而一般的獨占鎖是:讀讀互斥、讀寫互斥、寫寫互斥,而場景中往往讀遠遠大于寫,讀寫鎖就是為了這種優化而建立出來的一種機制。 注意是讀遠遠大于寫,一般情況下獨占鎖的效率低來源于高并發下對臨界區的激烈競争導緻線程上下文切換。是以當并發不是很高的情況下,讀寫鎖由于需要額外維護讀鎖的狀态,可能還不如獨占鎖的效率高。是以需要根據實際情況選擇使用。
在Java中ReadWriteLock的主要實作為ReentrantReadWriteLock,其提供了以下特性:
- 公平性選擇:支援公平與非公平(預設)的鎖擷取方式,吞吐量非公平優先于公平。
- 可重入:讀線程擷取讀鎖之後可以再次擷取讀鎖,寫線程擷取寫鎖之後可以再次擷取寫鎖。
- 可降級:寫線程擷取寫鎖之後,其還可以再次擷取讀鎖,然後釋放掉寫鎖,那麼此時該線程是讀鎖狀态,也就是降級操作。
4.24 volatile關鍵字有什麼用?
參考答案
當一個變量被定義成volatile之後,它将具備兩項特性:
-
保證可見性
當寫一個volatile變量時,JMM會把該線程本地記憶體中的變量強制重新整理到主記憶體中去,這個寫會操作會導緻其他線程中的volatile變量緩存無效。
-
禁止指令重排
使用volatile關鍵字修飾共享變量可以禁止指令重排序,volatile禁止指令重排序有一些規則:
- 當程式執行到volatile變量的讀操作或者寫操作時,在其前面的操作的更改肯定全部已經進行,且結果已經對後面的操作可見,在其後面的操作肯定還沒有進行;
- 在進行指令優化時,不能将對volatile變量通路的語句放在其後面執行,也不能把volatile變量後面的語句放到其前面執行。
即執行到volatile變量時,其前面的所有語句都執行完,後面所有語句都未執行。且前面語句的結果對volatile變量及其後面語句可見。
注意,雖然volatile能夠保證可見性,但它不能保證原子性。volatile變量在各個線程的工作記憶體中是不存在一緻性問題的,但是Java裡面的運算操作符并非原子操作,這導緻volatile變量的運算在并發下一樣是不安全的。
4.25 談談volatile的實作原理
- 它確定指令重排序時不會把其後面的指令排到記憶體屏障之前的位置,也不會把前面的指令排到記憶體屏障的後面;即在執行到記憶體屏障這句指令時,在它前面的操作已經全部完成;
- 它會強制将對緩存的修改操作立即寫入主存;
- 如果是寫操作,它會導緻其他CPU中對應的緩存行無效。