7、Thread 類中的 yield 方法有什麼做用?java
yield()應該作的是讓當前運行線程回到可運行狀態,以容許具備相同優先級的其餘線程得到運行機會。所以,使用yield()的目的是讓相同優先級的線程之間能適當的輪轉執行。可是,實際中沒法保證yield()達到讓步目的,由於讓步的線程還有可能被線程調度程序再次選中。git
結論:yield()從未致使線程轉到等待/睡眠/阻塞狀態。在大多數狀況下,yield()將致使線程從運行狀態轉到可運行狀態,但有可能沒有效果。面試
當鎖被釋放後,任何一個線程都有機會競爭獲得鎖,這樣作的目的是提升效率,但缺點是可能產生線程飢餓現象。安全
9、請談談 volatile 有什麼特色,爲何它能保證變量對全部線程的可見性?數據結構
volatile只能做用於變量,保證了操做可見性和有序性,不保證原子性。併發
在Java的內存模型中分爲主內存和工做內存,Java內存模型規定全部的變量存儲在主內存中,每條線程都有本身的工做內存。app
主內存和工做內存之間的交互分爲8個原子操做:框架
lockide
unlock
read
load
assign
use
store
write
volatile修飾的變量,只有對volatile進行assign操做,才能夠load,只有load才能夠use,,這樣就保證了在工做內存操做volatile變量,都會同步到主內存中。
10、爲何說 Synchronized 是一個悲觀鎖?樂觀鎖的實現原理又是什麼?什麼是 CAS,它有什麼特性?
Synchronized的併發策略是悲觀的,無論是否產生競爭,任何數據的操做都必須加鎖。
樂觀鎖的核心是CAS,CAS包括內存值、預期值、新值,只有當內存值等於預期值時,纔會將內存值修改成新值。
樂觀鎖認爲對一個對象的操做不會引起衝突,因此每次操做都不進行加鎖,只是在最後提交更改時驗證是否發生衝突,若是衝突則再試一遍,直至成功爲止,這個嘗試的過程稱爲自旋。
樂觀鎖沒有加鎖,但樂觀鎖引入了ABA問題,此時通常採用版本號進行控制;
也可能產生自旋次數過多問題,此時並不能提升效率,反而不如直接加鎖的效率高;
只能保證一個對象的原子性,能夠封裝成對象,再進行CAS操做;
12、請儘量詳盡地對比下 Synchronized 和 ReentrantLock 的異同。
一、類似點
它們都是阻塞式的同步,也就是說一個線程得到了對象鎖,進入代碼塊,其它訪問該同步塊的線程都必須阻塞在同步代碼塊外面等待,而進行線程阻塞和喚醒的代碼是比較高的。
二、功能區別
Synchronized是java語言的關鍵字,是原生語法層面的互斥,須要JVM實現;ReentrantLock 是JDK1.5以後提供的API層面的互斥鎖,須要lock和unlock()方法配合try/finally代碼塊來完成。
Synchronized使用較ReentrantLock 便利一些;
鎖的細粒度和靈活性:ReentrantLock強於Synchronized;
三、性能區別
Synchronized引入偏向鎖,自旋鎖以後,二者的性能差很少,在這種狀況下,官方建議使用Synchronized。
(1)Synchronized
Synchronized會在同步塊的先後分別造成monitorenter和monitorexit兩個字節碼指令。
在執行monitorenter指令時,首先要嘗試獲取對象鎖。若是這個對象沒被鎖定,或者當前線程已經擁有了那個對象鎖,把鎖的計數器+1,相應的執行monitorexit時,計數器-1,當計數器爲0時,鎖就會被釋放。若是獲取鎖失敗,當前線程就要阻塞,知道對象鎖被另外一個線程釋放爲止。
(2)ReentrantLock
ReentrantLock是java.util.concurrent包下提供的一套互斥鎖,相比Synchronized,ReentrantLock類提供了一些高級功能,主要有以下三項:
等待可中斷,持有鎖的線程長期不釋放的時候,正在等待的線程能夠選擇放棄等待,這至關於Synchronized避免出現死鎖的狀況。經過lock.lockInterruptibly()來實現這一機制;
公平鎖,多個線程等待同一個鎖時,必須按照申請鎖的時間順序得到鎖,Synchronized鎖是非公平鎖;ReentrantLock默認也是非公平鎖,能夠經過參數true設爲公平鎖,但公平鎖表現的性能不是很好;
鎖綁定多個條件,一個ReentrantLock對象能夠同時綁定多個對象。ReentrantLock提供了一個Condition(條件)類,用來實現分組喚醒須要喚醒的線程們,而不是像Synchronized要麼隨機喚醒一個線程,要麼喚醒所有線程。
一、什麼是可重入性
一個線程持有鎖時,當其餘線程嘗試獲取該鎖時,會被阻塞;而這個線程嘗試獲取本身持有鎖時,若是成功說明該鎖是可重入的,反之則不可重入。
二、synchronized是如何實現可重入性
synchronized關鍵字通過編譯後,會在同步塊的先後分別造成monitorenter和monitorexit兩個字節碼指令。每一個鎖對象內部維護一個計數器,該計數器初始值爲0,表示任何線程均可以獲取該鎖並執行相應的方法。根據虛擬機規範要求,在執行monitorenter指令時,首先要嘗試獲取對象的鎖,若是這個對象沒有被鎖定,或者當前線程已經擁有了對象的鎖,把鎖的計數器+1,相應的在執行monitorexit指令後鎖計數器-1,當計數器爲0時,鎖就被釋放。若是獲取對象鎖失敗,那當前線程就要阻塞等待,直到對象鎖被另外一個線程釋放爲止。
三、ReentrantLock如何實現可重入性
ReentrantLock使用內部類Sync來管理鎖,因此真正的獲取鎖是由Sync的實現類控制的。Sync有兩個實現,分別爲NonfairSync(非公公平鎖)和FairSync(公平鎖)。Sync經過繼承AQS實現,在AQS中維護了一個private volatile int state來計算重入次數,避免頻繁的持有釋放操做帶來的線程問題。
四、ReentrantLock代碼實例
// Sync繼承於AQS abstract static class Sync extends AbstractQueuedSynchronizer { ... } // ReentrantLock默認是非公平鎖 public ReentrantLock() { sync = new NonfairSync(); } // 能夠經過向構造方法中傳true來實現公平鎖 public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
protected final boolean tryAcquire(int acquires) {
// 當前想要獲取鎖的線程 final Thread current = Thread.currentThread(); // 當前鎖的狀態 int c = getState(); // state == 0 此時此刻沒有線程持有鎖 if (c == 0) { // 雖然此時此刻鎖是能夠用的,可是這是公平鎖,既然是公平,就得講究先來後到, // 看看有沒有別人在隊列中等了半天了 if (!hasQueuedPredecessors() && // 若是沒有線程在等待,那就用CAS嘗試一下,成功了就獲取到鎖了, // 不成功的話,只能說明一個問題,就在剛剛幾乎同一時刻有個線程搶先了 =_= // 由於剛剛還沒人的,我判斷過了 compareAndSetState(0, acquires)) { // 到這裏就是獲取到鎖了,標記一下,告訴你們,如今是我佔用了鎖 setExclusiveOwnerThread(current); return true; } } // 會進入這個else if分支,說明是重入了,須要操做:state=state+1 // 這裏不存在併發問題 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } // 若是到這裏,說明前面的if和else if都沒有返回true,說明沒有獲取到鎖 return false; }
?五、代碼分析 當一個線程在獲取鎖過程當中,先判斷state的值是否爲0,若是是表示沒有線程持有鎖,就能夠嘗試獲取鎖。 當state的值不爲0時,表示鎖已經被一個線程佔用了,這時會作一個判斷current==getExclusiveOwnerThread(),這個方法返回的是當前持有鎖的線程,這個判斷是看當前持有鎖的線程是否是本身,若是是本身,那麼將state的值+1,表示重入返回便可。 十4、什麼是鎖消除和鎖粗化? -------------- 一、鎖消除 所消除就是虛擬機根據一個對象是否真正存在同步狀況,若不存在同步狀況,則對該對象的訪問無需通過加鎖解鎖的操做。 好比StringBuffer的append方法,由於append方法須要判斷對象是否被佔用,而若是代碼不存在鎖競爭,那麼這部分的性能消耗是無心義的。因而虛擬機在即時編譯的時候就會將上面的代碼進行優化,也就是鎖消除。
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null; super.append(str); return this;
}
從源碼能夠看出,append方法用了?synchronized關鍵字,它是線程安全的。但咱們可能僅在線程內部把StringBuffer當作局部變量使用;StringBuffer僅在方法內做用域有效,不存在線程安全的問題,這時咱們能夠經過編譯器將其優化,將鎖消除,前提是Java必須運行在server模式,同時必須開啓逃逸分析;
-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks
其中+DoEscapeAnalysis表示開啓逃逸分析,+EliminateLocks表示鎖消除。
public static String createStringBuffer(String str1, String str2) { StringBuffer sBuf = new StringBuffer(); sBuf.append(str1);// append方法是同步操做 sBuf.append(str2); return sBuf.toString(); } ``` 逃逸分析:好比上面的代碼,它要看sBuf是否可能逃出它的做用域?若是將sBuf做爲方法的返回值進行返回,那麼它在方法外部可能被看成一個全局對象使用,就有可能發生線程安全問題,這時就能夠說sBuf這個對象發生逃逸了,於是不該將append操做的鎖消除,但咱們上面的代碼沒有發生鎖逃逸,鎖消除就能夠帶來必定的性能提高。? 二、鎖粗化 鎖的請求、同步、釋放都會消耗必定的系統資源,若是高頻的鎖請求反而不利於系統性能的優化,鎖粗化就是把屢次的鎖請求合併成一個請求,擴大鎖的範圍,下降鎖請求、同步、釋放帶來的性能損耗。 十5、跟 Synchronized 相比,可重入鎖 ReentrantLock 其實現原理有什麼不一樣? --------------------------------------------------- 一、都是可重入鎖; 二、ReentrantLock內部是實現了Sync,Sync繼承於AQS抽象類。Sync有兩個實現,一個是公平鎖,一個是非公平鎖,經過構造函數定義。AQS中維護了一個state來計算重入次數,避免頻繁的持有釋放操做帶來的線程問題。 三、ReentrantLock只能定義代碼塊,而Synchronized能夠定義方法和代碼塊; 四、Synchronized是JVM的一個內部關鍵字,ReentrantLock是JDK1.5以後引入的一個API層面的互斥鎖; 五、Synchronized實現自動的加鎖、釋放鎖,ReentrantLock須要手動加鎖和釋放鎖,中間能夠暫停; 六、Synchronized因爲引進了偏向鎖和自旋鎖,因此性能上和ReentrantLock差很少,但操做上方便不少,因此優先使用Synchronized。 [](https://gitee.com/vip204888/java-p7)十6、那麼請談談 AQS 框架是怎麼回事兒? ------------------------------------------------------------------------------------- 一、AQS是AbstractQueuedSynchronizer的縮寫,它提供了一個FIFO隊列,能夠當作是一個實現同步鎖的核心組件。 AQS是一個抽象類,主要經過繼承的方式來使用,它自己沒有實現任何的同步接口,僅僅是定義了同步狀態的獲取和釋放的方法來提供自定義的同步組件。 二、AQS的兩種功能:獨佔鎖和共享鎖 三、AQS的內部實現 AQS的實現依賴內部的同步隊列,也就是FIFO的雙向隊列,若是當前線程競爭失敗,那麼AQS會把當前線程以及等待狀態信息構形成一個Node加入到同步隊列中,同時再阻塞該線程。當獲取鎖的線程釋放鎖之後,會從隊列中喚醒一個阻塞的節點(線程)。 ![](https://img-blog.csdnimg.cn/20210529205800577.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2d1b3J1aV9qYXZh,size_16,color_FFFFFF,t_70) AQS隊列內部維護的是一個FIFO的雙向鏈表,這種結構的特色是每一個數據結構都有兩個指針,分別指向直接的後繼節點和直接前驅節點。因此雙向鏈表能夠從任意一個節點開始很方便的範文前驅和後繼節點。每一個Node實際上是由線程封裝,當線程爭搶鎖失敗後會封裝成Node加入到AQS隊列中。 [](https://gitee.com/vip204888/java-p7)十7、AQS 對資源的共享方式? ------------------------------------------------------------------------------- AQS定義兩種資源共享方式 * Exclusive(獨佔):只有一個線程能執行,如ReentrantLock。又可分爲公平鎖和非公平鎖: * 公平鎖:按照線程在隊列中的排隊順序,先到者先拿到鎖 * 非公平鎖:當線程要獲取鎖時,無視隊列順序直接去搶鎖,誰搶到就是誰的 * **Share**(共享):多個線程可同時執行,如Semaphore/CountDownLatch。Semaphore、CountDownLatch、 CyclicBarrier、ReadWriteLock 咱們都會在後面講到。 ReentrantReadWriteLock 能夠當作是組合式,由於ReentrantReadWriteLock也就是讀寫鎖容許多個線程同時對某一資源進行讀。 不一樣的自定義同步器爭用共享資源的方式也不一樣。自定義同步器在實現時只須要實現共享資源 state 的獲取與釋放方式便可,至於具體線程等待隊列的維護(如獲取資源失敗入隊/喚醒出隊等),AQS已經在頂層實現好了。 [](https://gitee.com/vip204888/java-p7)十8、如何讓 Java 的線程彼此同步? ----------------------------------------------------------------------------------- 1. synchronized 2. volatile 3. ReenreantLock 4. 使用局部變量實現線程同步 [](https://gitee.com/vip204888/java-p7)十9、你瞭解過哪些同步器?請分別介紹下。 ----------------------------------------------------------------------------------- 一、Semaphore同步器 特徵: 經典的信號量,經過計數器控制對共享資源的訪問 Semaphore(int count):建立擁有count個許可證的信號量 acquire()/acquire(int num) : 獲取1/num個許可證 release/release(int num) : 釋放1/num個許可證 二、CountDownLatch同步器 特徵: 必須發生指定數量的事件後才能夠繼續運行(好比賽跑比賽,裁判喊出3,2,1以後你們才同時跑) CountDownLatch(int count):必須發生count個數量才能夠打開鎖存器 await:等待鎖存器 countDown:觸發事件 三、CyclicBarrier同步器 特徵: 適用於只有多個線程都到達預約點時才能夠繼續執行(好比鬥地主,須要等齊三我的纔開始) CyclicBarrier(int num) :等待線程的數量 CyclicBarrier(int num, Runnable action) :等待線程的數量以及全部線程到達後的操做 await() : 到達臨界點後暫停線程 四、交換器(Exchanger)同步器 五、Phaser同步器 [](https://gitee.com/vip204888/java-p7)二10、Java 中的線程池是如何實現的 ---------------------------------------------------------------------------------- 建立一個阻塞隊列來容納任務,在第一次執行任務時建立足夠多的線程,並處理任務,以後每一個工做線程自動從任務隊列中獲取線程,直到任務隊列中任務爲0爲止,此時線程處於等待狀態,一旦有工做任務加入任務隊列中,即刻喚醒工做線程進行處理,實現線程的可複用性。 線程池通常包括四個基本組成部分: 一、線程池管理器 用於建立線程池,銷燬線程池,添加新任務。 二、工做線程 線程池中線程,可循環執行任務,在沒有任務時處於等待狀態。 三、任務隊列 用於存放沒有處理的任務,一種緩存機制。 四、任務接口 每一個任務必須實現的接口,供工做線程調度任務的執行,主要規定了任務的開始和收尾工做,和任務的狀態。 二11、建立線程池的幾個核心構造參數 ------------------ ``` // Java線程池的完整構造函數 public ThreadPoolExecutor( int corePoolSize, // 線程池長期維持的最小線程數,即便線程處於Idle狀態,也不會回收。 int maximumPoolSize, // 線程數的上限 long keepAliveTime, // 線程最大生命週期。 TimeUnit unit, //時間單位 BlockingQueue<Runnable> workQueue, //任務隊列。當線程池中的線程都處於運行狀態,而此時任務數量繼續增長,則須要一個容器來容納這些任務,這就是任務隊列。 ThreadFactory threadFactory, // 線程工廠。定義如何啓動一個線程,能夠設置線程名稱,而且能夠確認是不是後臺線程等。 RejectedExecutionHandler handler // 拒絕任務處理器。因爲超出線程數量和隊列容量而對繼續增長的任務進行處理的程序。 ) ``` 二12、線程池中的線程是怎麼建立的?是一開始就隨着線程池的啓動建立好的嗎? ------------------------------------- 線程池中的線程是在第一次提交任務submit時建立的 建立線程的方式有繼承Thread和實現Runnable,重寫run方法,start開始執行,wait等待,sleep休眠,shutdown中止。 一、newSingleThreadExecutor:單線程池。 顧名思義就是一個池中只有一個線程在運行,該線程永不超時,並且因爲是一個線程,當有多個任務須要處理時,會將它們放置到一個無界阻塞隊列中逐個處理,它的實現代碼以下: ``` public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable())); } ``` 它的使用方法也很簡單,下面是簡單的示例: ``` public static void main(String[] args) throws ExecutionException,InterruptedException { // 建立單線程執行器 ExecutorService es = Executors.newSingleThreadExecutor(); // 執行一個任務 Future<String> future = es.submit(new Callable<String>() { @Override public String call() throws Exception { return ""; } }); // 得到任務執行後的返回值 System.out.println("返回值:" + future.get()); // 關閉執行器 es.shutdown(); } ``` 二、newCachedThreadPool:緩衝功能的線程。 創建了一個線程池,並且線程數量是沒有限制的(固然,不能超過Integer的最大值),新增一個任務即有一個線程處理,或者複用以前空閒的線程,或者重親啓動一個線程,可是一旦一個線程在60秒內一直處於等待狀態時(也就是一分鐘無事可作),則會被終止,其源碼以下: ### 最後 **因爲篇幅限制,小編在此截出幾張知識講解的圖解,有須要的程序猿(媛)能夠點贊後[戳這裏免費領取所有資料](https://gitee.com/vip204888/java-p7)獲取哦** ![P8級大佬整理在Github上45K+star手冊,吃透消化,面試跳槽不心慌](https://upload-images.jianshu.io/upload_images/24616006-fc1cbc1ca7186f03?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![P8級大佬整理在Github上45K+star手冊,吃透消化,面試跳槽不心慌](https://upload-images.jianshu.io/upload_images/24616006-7d9a9d4b2a67dcad?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![P8級大佬整理在Github上45K+star手冊,吃透消化,面試跳槽不心慌](https://upload-images.jianshu.io/upload_images/24616006-c054c80f9d5697ae?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![P8級大佬整理在Github上45K+star手冊,吃透消化,面試跳槽不心慌](https://upload-images.jianshu.io/upload_images/24616006-b04277920e45fb1a?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![P8級大佬整理在Github上45K+star手冊,吃透消化,面試跳槽不心慌](https://upload-images.jianshu.io/upload_images/24616006-81e94a8066f94cae?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)