日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

多个线程访问统一对象的不同方法_不会多线程还想进BAT?精选19道多线程面试题,有答案边看边学...

發(fā)布時(shí)間:2023/12/15 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 多个线程访问统一对象的不同方法_不会多线程还想进BAT?精选19道多线程面试题,有答案边看边学... 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一. Java程序如何停止一個(gè)線程?

建議使用”異常法”來終止線程的繼續(xù)運(yùn)行。在想要被中斷執(zhí)行的線程中, 調(diào)用 interrupted()方法,該方法用來檢驗(yàn)當(dāng)前線程是否已經(jīng)被中斷,即該線程 是否被打上了中斷的標(biāo)記,并不會(huì)使得線程立即停止運(yùn)行,如果返回 true,則 拋出異常,停止線程的運(yùn)行。在線程外,調(diào)用 interrupt()方法,使得該線程打 上中斷的標(biāo)記。

二. 說一下 java 中的多線程。

1. Java 中實(shí)現(xiàn)多線程的四種方式(創(chuàng)建多線程的四種方式)?

①. 繼承 Thread 類創(chuàng)建線程類

  • 定義 Thread 類的子類,并重寫該類的 run 方法,該 run 方法的方 法體就代表了線程要完成的任務(wù)。因此把 run()方法稱為執(zhí)行體。
  • 創(chuàng)建 Thread 子類的實(shí)例,即創(chuàng)建了線程對(duì)象。
  • 調(diào)用線程對(duì)象的 start()方法來啟動(dòng)該線程。

②. 通過 Runnable 接口創(chuàng)建線程類

  • 定義 Runnable 接口的實(shí)現(xiàn)類,并重寫該接口的 run()方法,該 run() 方法的方法體同樣是該線程的線程執(zhí)行體。
  • 創(chuàng)建 Runnable 實(shí)現(xiàn)類的實(shí)例,并依此實(shí)例作為 Thread 的 target 來創(chuàng)建 Thread 對(duì)象,該 Thread 對(duì)象才是真正的線程對(duì)象。
  • 調(diào)用線程對(duì)象的 start()方法來啟動(dòng)該線程。

③. 通過 Callable 和 Future 創(chuàng)建線程

  • 創(chuàng)建 Callable 接口的實(shí)現(xiàn)類,并實(shí)現(xiàn) call()方法,該 call()方法將作 為線程執(zhí)行體,并且有返回值。
  • 創(chuàng)建 Callable 實(shí)現(xiàn)類的實(shí)例,使用 FutureTask 類來包裝 Callable 對(duì)象,該 FutureTask 對(duì)象封裝了該 Callable 對(duì)象的 call()方法的返回值。
  • 使用 FutureTask 對(duì)象作為 Thread 對(duì)象的 target 創(chuàng)建并啟動(dòng)新線 程。
  • 調(diào)用 FutureTask 對(duì)象的 get()方法來獲得子線程執(zhí)行結(jié)束后的返回值。

④. 通過線程池創(chuàng)建線程

利用線程池不用 new 就可以創(chuàng)建線程,線程可復(fù)用,利用 Executors 創(chuàng) 建線程池。

擴(kuò)展1:Java 中 Runnable 和 Callable 有什么不同?

  • Callable 定義的方法是 call(),而 Runnable 定義的方法是 run()。
  • Callable 的 call 方法可以有返回值,而 Runnable 的 run 方法不能有 返回值。
  • Callable 的 call 方法可拋出異常,而 Runnable 的 run 方法不能拋出 異常。

擴(kuò)展2:一個(gè)類是否可以同時(shí)繼承 Thread 和實(shí)現(xiàn) Runnable接口?<

可以。比如下面的程序可以通過編譯。因?yàn)?Test 類從 Thread 類中繼承了 run()方法,這個(gè) run()方法可以被當(dāng)作對(duì) Runnable 接口的實(shí)現(xiàn)。

public class Test extends Thread implements Runnable { public static void main(String[] args) { Thread t = new Thread(new Test()); t.start(); }}

2. 實(shí)現(xiàn)多線程的同步。

在多線程的環(huán)境中,經(jīng)常會(huì)遇到數(shù)據(jù)的共享問題,即當(dāng)多個(gè)線程需要訪問同 一資源時(shí),他們需要以某種順序來確保該資源在某一時(shí)刻只能被一個(gè)線程使用, 否則,程序的運(yùn)行結(jié)果將會(huì)是不可預(yù)料的,在這種情況下,就必須對(duì)數(shù)據(jù)進(jìn)行 同步。

在 Java 中,提供了四種方式來實(shí)現(xiàn)同步互斥訪問: synchronized 和 Lock 和 wait()/notify()/notifyAll()方法和 CAS。

①. synchronized 的用法

A . 同步代碼塊

synchronized 塊寫法: synchronized(object) {}

表示線程在執(zhí)行的時(shí)候會(huì)將 object 對(duì)象上鎖。(注意這個(gè)對(duì)象可以是任意 類的對(duì)象,也可以使用 this 關(guān)鍵字或者是 class 對(duì)象)。

可能一個(gè)方法中只有幾行代碼會(huì)涉及到線程同步問題,所以 synchronized 塊 比 synchronized 方法更加細(xì)粒度地控制了多個(gè)線程的訪問, 只有 synchronized 塊中的內(nèi)容不能同時(shí)被多個(gè)線程所訪問,方法中的其他語句仍然 可以同時(shí)被多個(gè)線程所訪問(包括 synchronized 塊之前的和之后的)。

B . 修飾非靜態(tài)的方法

當(dāng) synchronized 關(guān)鍵字修飾一個(gè)方法的時(shí)候,該方法叫做同步方法。

Java 中的每個(gè)對(duì)象都有一個(gè)鎖(lock),或者叫做監(jiān)視器(monitor), 當(dāng)一個(gè)線程訪問某個(gè)對(duì)象的 synchronized 方法時(shí),將該對(duì)象上鎖,其他任何 線程都無法再去訪問該對(duì)象的 synchronized 方法了(這里是指所有的同步方 法,而不僅僅是同一個(gè)方法),直到之前的那個(gè)線程執(zhí)行方法完畢后(或者是 拋出了異常),才將該對(duì)象的鎖釋放掉,其他線程才有可能再去訪問該對(duì)象的 synchronized 方法。

注意這時(shí)候是給對(duì)象上鎖,如果是不同的對(duì)象,則各個(gè)對(duì)象之間沒有限制 關(guān)系。

注意,如果一個(gè)對(duì)象有多個(gè) synchronized 方法,某一時(shí)刻某個(gè)線程已經(jīng)進(jìn)入 到了某個(gè) synchronized 方法,那么在該方法沒有執(zhí)行完畢前,其他線程是無法訪 問該對(duì)象的任何 synchronized 方法的。

C . 修飾靜態(tài)的方法

當(dāng)一個(gè) synchronized 關(guān)鍵字修飾的方法同時(shí)又被 static 修飾,之前說過, 非靜態(tài)的同步方法會(huì)將對(duì)象上鎖,但是靜態(tài)方法不屬于對(duì)象,而是屬于類,它 會(huì)將這個(gè)方法所在的類的 Class 對(duì)象上鎖。一個(gè)類不管生成多少個(gè)對(duì)象,它們 所對(duì)應(yīng)的是同一個(gè) Class 對(duì)象。

因此,當(dāng)線程分別訪問同一個(gè)類的兩個(gè)對(duì)象的兩個(gè) static,synchronized 方法時(shí),它們的執(zhí)行順序也是順序的,也就是說一個(gè)線程先去執(zhí)行方法,執(zhí)行 完畢后另一個(gè)線程才開始。

結(jié)論:

  • synchronized 方法是一種粗粒度的并發(fā)控制,某一時(shí)刻,只能有一個(gè)線 程執(zhí)行該 synchronized 方法。
  • synchronized 塊則是一種細(xì)粒度的并發(fā)控制,只會(huì)將塊中的代碼同步, 位于方法內(nèi),synchronized 塊之外的其他代碼是可以被多個(gè)線程同時(shí)訪問到 的。

②.Lock 的用法

使用 Lock 必須在 try-catch-finally 塊中進(jìn)行,并且將釋放鎖的操作放在 finally 塊中進(jìn)行,以保證鎖一定被釋放,防止死鎖的發(fā)生。通常使用 Lock 來 進(jìn)行同步的話,是以下面這種形式去使用的:

Lock lock = ...;lock.lock();try{ //處理任務(wù)}catch(Exception ex){}finally{ lock.unlock(); //釋放鎖}

Lock 和 synchronized 的區(qū)別和 Lock 的優(yōu)勢(shì)。你需要實(shí)現(xiàn) 一個(gè)高效的緩存,它允許多個(gè)用戶讀,但只允許一個(gè)用戶寫,以此 來保持它的完整性,你會(huì)怎樣去實(shí)現(xiàn)它?

  • Lock 是一個(gè)接口,而 synchronized 是 Java 中的關(guān)鍵字, synchronized 是內(nèi)置的語言實(shí)現(xiàn);
  • synchronized 在發(fā)生異常時(shí),會(huì)自動(dòng)釋放線程占有的鎖,因此不會(huì)導(dǎo) 致死鎖現(xiàn)象發(fā)生;而 Lock 在發(fā)生異常時(shí),如果沒有主動(dòng)通過 unLock()去釋放 鎖,則很可能造成死鎖現(xiàn)象,因此使用 Lock 時(shí)需要在 finally 塊中釋放鎖;
  • Lock 可以讓等待鎖的線程響應(yīng)中斷(可中斷鎖),而 synchronized 卻不行,使用 synchronized 時(shí),等待的線程會(huì)一直等待下去,不能夠響應(yīng)中 斷(不可中斷鎖);
  • 通過 Lock 可以知道有沒有成功獲取鎖(tryLock()方法:如果獲取 了鎖,則返回 true;否則返回 false,也就說這個(gè)方法無論如何都會(huì)立即返回。 在拿不到鎖時(shí)不會(huì)一直在那等待。),而 synchronized 卻無法辦到。
  • Lock 可以提高多個(gè)線程進(jìn)行讀操作的效率(讀寫鎖)。
  • Lock 可以實(shí)現(xiàn)公平鎖,synchronized 不保證公平性。 在性能上來說,如果線程競(jìng)爭(zhēng)資源不激烈時(shí),兩者的性能是差不多的,而 當(dāng)競(jìng)爭(zhēng)資源非常激烈時(shí)(即有大量線程同時(shí)競(jìng)爭(zhēng)),此時(shí) Lock 的性能要遠(yuǎn)遠(yuǎn)優(yōu) 于 synchronized。所以說,在具體使用時(shí)要根據(jù)適當(dāng)情況選擇。

擴(kuò)展1: volatile 和 synchronized 區(qū)別。

  • volatile 是變量修飾符,而 synchronized 則作用于代碼塊或方法。
  • volatile 不會(huì)對(duì)變量加鎖,不會(huì)造成線程的阻塞;synchronized 會(huì) 對(duì)變量加鎖,可能會(huì)造成線程的阻塞。
  • volatile 僅能實(shí)現(xiàn)變量的修改可見性,并不能保證原子性;而 synchronized 則 可 以 保 證 變 量 的 修 改 可 見 性 和 原 子 性 。 (synchronized 有兩個(gè)重要含義:它確保了一次只有一個(gè)線程可以執(zhí) 行代碼的受保護(hù)部分(互斥),而且它確保了一個(gè)線程更改的數(shù)據(jù)對(duì)于 其它線程是可見的(更改的可見性),在釋放鎖之前會(huì)將對(duì)變量的修改 刷新到主存中)。
  • volatile 標(biāo)記的變量不會(huì)被編譯器優(yōu)化,禁止指令重排序; synchronized 標(biāo)記的變量可以被編譯器優(yōu)化。

擴(kuò)展 2:什么場(chǎng)景下可以使用 volatile 替換 synchronized?

只需要保證共享資源的可見性的時(shí)候可以使用 volatile 替代, synchronized 保證可操作的原子性,一致性和可見性。

③.wait()otify()otifyAll()的用法(Java 中怎樣喚醒一個(gè)阻塞的線程?)

在 Java 發(fā)展史上曾經(jīng)使用 suspend()、resume()方法對(duì)于線程進(jìn)行阻塞喚醒,但隨之出 現(xiàn)很多問題,比較典型的還是死鎖問題。

解決方案可以使用以對(duì)象為目標(biāo)的阻塞,即利用 Object 類的 wait()和 notify()方法實(shí)現(xiàn) 線程阻塞。

首先,wait、notify 方法是針對(duì)對(duì)象的,調(diào)用任意對(duì)象的 wait()方法都將導(dǎo)致線程阻塞, 阻塞的同時(shí)也將釋放該對(duì)象的鎖,相應(yīng)地,調(diào)用任意對(duì)象的 notify()方法則將隨機(jī)解除該對(duì) 象阻塞的線程,但它需要重新獲取改對(duì)象的鎖,直到獲取成功才能往下執(zhí)行;其次,wait、 notify 方法必須在 synchronized 塊或方法中被調(diào)用,并且要保證同步塊或方法的鎖對(duì)象與調(diào) 用 wait、notify 方法的對(duì)象是同一個(gè),如此一來在調(diào)用 wait 之前當(dāng)前線程就已經(jīng)成功獲取 某對(duì)象的鎖,執(zhí)行 wait 阻塞后當(dāng)前線程就將之前獲取的對(duì)象鎖釋放。

擴(kuò)展 1: 為什么 wait(),notify(),notifyAll()等方法都定義在 Object 類中?

因?yàn)檫@三個(gè)方法都需要定義在同步代碼塊或同步方法中,這些方法的調(diào)用是依賴鎖對(duì) 象的,而同步代碼塊或同步方法中的鎖對(duì)象可以是任意對(duì)象,那么能被任意對(duì)象調(diào)用的方 法一定定義在 Object 類中。

擴(kuò)展 2: notify()和 notifyAll()有什么區(qū)別?

notify()和 notifyAll()都是 Object 對(duì)象用于通知處在等待該對(duì)象的線程的方法。

void notify(): 喚醒一個(gè)正在等待該對(duì)象的線程,進(jìn)入就緒隊(duì)列等待 CPU 的調(diào)度。

void notifyAll(): 喚醒所有正在等待該對(duì)象的線程,進(jìn)入就緒隊(duì)列等待 CPU 的調(diào)度。

兩者的最大區(qū)別在于:

notifyAll 使所有原來在該對(duì)象上等待被 notify 的線程統(tǒng)統(tǒng)退出 wait 的狀態(tài),變成等待該對(duì) 象上的鎖,一旦該對(duì)象被解鎖,他們就會(huì)去競(jìng)爭(zhēng)。 notify 他只是選擇一個(gè) wait 狀態(tài)線程進(jìn)行通知,并使它獲得該對(duì)象上的鎖,但不驚動(dòng)其他 同樣在等待被該對(duì)象 notify 的線程們,當(dāng)?shù)谝粋€(gè)線程運(yùn)行完畢以后釋放對(duì)象上的鎖,此時(shí) 如果該對(duì)象沒有再次使用 notify 語句,即便該對(duì)象已經(jīng)空閑,其他 wait 狀態(tài)等待的線程由 于沒有得到該對(duì)象的通知,繼續(xù)處在 wait 狀態(tài),直到這個(gè)對(duì)象發(fā)出一個(gè) notify 或 notifyAll, 它們等待的是被 notify 或 notifyAll,而不是鎖。

④.CAS

它是一種非阻塞的同步方式。具體參見上面的部分。

擴(kuò)展一:同步鎖的分類?

  • Synchronized 和 Lock 都是悲觀鎖。
  • 樂觀鎖,CAS 同步原語,如原子類,非阻塞同步方式。

擴(kuò)展二:鎖的分類?

  • 一種是代碼層次上的,如 java 中的同步鎖,可重入鎖,公平鎖,讀寫鎖。另外一種是數(shù)據(jù)庫層次上的,比較典型的就是悲觀鎖和樂觀鎖,表鎖,行鎖,頁鎖。

擴(kuò)展三:java 中的悲觀鎖和樂觀鎖?

  • 悲觀鎖:悲觀鎖是認(rèn)為肯定有其他線程來爭(zhēng)奪資源,因此不管到底會(huì)不會(huì)發(fā)生爭(zhēng)奪, 悲觀鎖總是會(huì)先去鎖住資源,會(huì)導(dǎo)致其它所有需要鎖的線程掛起,等待持有鎖的線程釋放 鎖。Synchronized 和 Lock 都是悲觀鎖。
  • 樂觀鎖:每次不加鎖,假設(shè)沒有沖突去完成某項(xiàng)操作,如果因?yàn)闆_突失敗就重試,直 到成功為止。就是當(dāng)去做某個(gè)修改或其他操作的時(shí)候它認(rèn)為不會(huì)有其他線程來做同樣的操 作(競(jìng)爭(zhēng)),這是一種樂觀的態(tài)度,通常是基于 CAS 原子指令來實(shí)現(xiàn)的。CAS 通常不會(huì)將 線程掛起,因此有時(shí)性能會(huì)好一些。樂觀鎖的一種實(shí)現(xiàn)方式——CAS。

三. 實(shí)現(xiàn)線程之間的通信?

當(dāng)線程間是可以共享資源時(shí),線程間通信是協(xié)調(diào)它們的重要的手段。

1. Object 類中 wait()otify()otifyAll()方法

2. 用 Condition 接口

  • Condition 是被綁定到 Lock 上的,要?jiǎng)?chuàng)建一個(gè) Lock 的 Condition 對(duì) 象必須用 newCondition()方法。在一個(gè) Lock 對(duì)象里面可以創(chuàng)建多個(gè) Condition 對(duì)象,線程可以注冊(cè)在指定的 Condition 對(duì)象中,從而可以有 選擇性地進(jìn)行線程通知,在線程調(diào)度上更加靈活。
  • 在 Condition 中,用 await()替換 wait(),用 signal()替換 notify(), 用 signalAll()替換 notifyAll(),傳統(tǒng)線程的通信方式, Condition 都可以實(shí)現(xiàn)。 調(diào)用 Condition 對(duì)象中的方法時(shí),需要被包含在 lock()和 unlock()之間。

3. 管道實(shí)現(xiàn)線程間的通信

  • 實(shí)現(xiàn)方式:一個(gè)線程發(fā)送數(shù)據(jù)到輸出管道流,另一個(gè)線程從輸入管道流中 讀取數(shù)據(jù)。
  • 基本流程:
  • 1)創(chuàng)建管道輸出流 PipedOutputStream pos 和管道輸入流 PipedInputStream pis。
  • 2)將 pos 和 pis 匹配,pos.connect(pis)。
  • 3)將 pos 賦給信息輸入信息的線程,pis 賦給獲取信息的線程,就可以實(shí) 現(xiàn)線程間的通訊了。
  • 缺點(diǎn):
  • 1)管道流只能在兩個(gè)線程之間傳遞數(shù)據(jù)。
  • 線程 consumer1 和 consumer2 同時(shí)從 pis 中 read 數(shù)據(jù),當(dāng)線程 producer 往管道流中寫入一段數(shù)據(jù)(1,2,3,4,5,6)后,每一個(gè)時(shí)刻只有一個(gè) 線程能獲取到數(shù)據(jù),并不是兩個(gè)線程都能獲取到 producer 發(fā)送來的數(shù)據(jù),因 此一個(gè)管道流只能用于兩個(gè)線程間的通訊。
  • 2)管道流只能實(shí)現(xiàn)單向發(fā)送,如果要兩個(gè)線程之間互通訊,則需要兩個(gè)管道流。
  • 線程 producer 通過管道流向線程 consumer 發(fā)送數(shù)據(jù),如果線程 consumer 想給線程 producer 發(fā)送數(shù)據(jù),則需要新建另一個(gè)管道流 pos1 和 pis1,將 pos1 賦給 consumer1,將 pis1 賦給 producer1。

4. 使用 volatile 關(guān)鍵字

見上面部分。

四. 如何確保線程安全?

如果多個(gè)線程同時(shí)運(yùn)行某段代碼,如果每次運(yùn)行結(jié)果和單線程運(yùn)行的結(jié)果 是一樣的,而且其他變量的值也和預(yù)期的是一樣的,就是線程安全的。

Synchronized,Lock,原子類(如 atomicinteger 等),同步容器、并 發(fā)容器、 阻塞隊(duì)列 、 同步輔助類(比 如 CountDownLatch, Semaphore, CyclicBarrier)。

五. 多線程的優(yōu)點(diǎn)和缺點(diǎn)?

1. 優(yōu)點(diǎn):

  • 充分利用 cpu,避免 cpu 空轉(zhuǎn)。
  • 程序響應(yīng)更快。

2. 缺點(diǎn):

  • 上下文切換的開銷
  • 當(dāng) CPU 從執(zhí)行一個(gè)線程切換到執(zhí)行另外一個(gè)線程的時(shí)候,它需要先存儲(chǔ)當(dāng) 前線程的本地的數(shù)據(jù),程序指針等,然后載入另一個(gè)線程的本地?cái)?shù)據(jù),程序指 針等,最后才開始執(zhí)行。這種切換稱為“上下文切換”。CPU 會(huì)在一個(gè)上下文 中執(zhí)行一個(gè)線程,然后切換到另外一個(gè)上下文中執(zhí)行另外一個(gè)線程。上下文切 換并不廉價(jià)。如果沒有必要,應(yīng)該減少上下文切換的發(fā)生。
  • 增加資源消耗
  • 線程在運(yùn)行的時(shí)候需要從計(jì)算機(jī)里面得到一些資源。 除了 CPU,線程還需 要一些內(nèi)存來維持它本地的堆棧。它也需要占用操作系統(tǒng)中一些資源來管理線 程。
  • 編程更復(fù)雜
  • 在多線程訪問共享數(shù)據(jù)的時(shí)候,要考慮線程安全問題。

六. 寫出 3 條你遵循的多線程最佳實(shí)踐。

  • 給線程起個(gè)有意義的名字。
  • 避免鎖定和縮小同步的范圍 。 相對(duì)于同步方法我更喜歡同步塊,它給我擁有對(duì)鎖的絕對(duì)控制權(quán)。
  • 多用同步輔助類,少用 wait 和 notify 。 首先,CountDownLatch, Semaphore, CyclicBarrier 這些同步輔助 類簡(jiǎn)化了編碼操作,而用 wait 和 notify 很難實(shí)現(xiàn)對(duì)復(fù)雜控制流的控制。其次, 這些類是由最好的企業(yè)編寫和維護(hù)在后續(xù)的 JDK 中它們還會(huì)不斷優(yōu)化和完善, 使用這些更高等級(jí)的同步工具你的程序可以不費(fèi)吹灰之力獲得優(yōu)化。
  • 多用并發(fā)容器,少用同步容器。 如果下一次你需要用到 map,你應(yīng)該首先想到用 ConcurrentHashMap。
  • 七. 多線程的性能一定就優(yōu)于單線程嗎?

    不一定,要看具體的任務(wù)以及計(jì)算機(jī)的配置。比如說:

    • 對(duì)于單核 CPU,如果是 CPU 密集型任務(wù),如解壓文件,多線程的性能反 而不如單線程性能,因?yàn)榻鈮何募枰恢闭加?CPU 資源,如果采用多線程, 線程切換導(dǎo)致的開銷反而會(huì)讓性能下降。如果是交互類型的任務(wù),肯定是需要 使用多線程的。
    • 對(duì)于多核 CPU,對(duì)于解壓文件來說,多線程肯定優(yōu)于單線程,因?yàn)槎鄠€(gè)線 程能夠更加充分利用每個(gè)核的資源。

    八. 多線程中鎖的種類。

    1. 可重入鎖

    ReentrantLock 和 synchronized 都是可重入鎖。

    如果當(dāng)前線程已經(jīng)獲得了某個(gè)監(jiān)視器對(duì)象所持有的鎖,那么該線程在該方法 中調(diào)用另外一個(gè)同步方法也同樣持有該鎖。

    比如:

    public sychrnozied void test() { xxxxxx; test2();}public sychronized void test2() { yyyyy;}

    在上面代碼段中,執(zhí)行 test 方法需要獲得當(dāng)前對(duì)象作為監(jiān)視器的對(duì)象鎖, 但方法中又調(diào)用了 test2 的同步方法。

    如果鎖是具有可重入性的話,那么該線程在調(diào)用 test2 時(shí)并不需要再次獲 得當(dāng)前對(duì)象的鎖,可以直接進(jìn)入 test2 方法進(jìn)行操作。

    可重入鎖最大的作用是避免死鎖。如果鎖是不具有可重入性的話,那么該 線程在調(diào)用 test2 前會(huì)等待當(dāng)前對(duì)象鎖的釋放,實(shí)際上該對(duì)象鎖已被當(dāng)前線程 所持有,不可能再次獲得,那么線程在調(diào)用同步方法、含有鎖的方法時(shí)就會(huì)產(chǎn) 生死鎖。

    2. 可中斷鎖

    顧名思義,就是可以響應(yīng)中斷的鎖。

    在 Java 中,synchronized 不是可中斷鎖,而 Lock 是可中斷鎖。 lockInterruptibly()的用法已經(jīng)體現(xiàn)了 Lock 的可中斷性。如果某一線程 A 正 在執(zhí)行鎖中的代碼,另一線程 B 正在等待獲取該鎖,可能由于等待時(shí)間過長, 線程 B 不想等待了,想先處理其他事情,我們可以讓它中斷自己或者在別的線 程中斷它,這種就是可中斷鎖。

    3. 公平鎖

    在 Java 中,synchronized 就是非公平鎖,它無法保證等待的線程獲取鎖 的順序。而對(duì)于 ReentrantLock 和 ReentrantReadWriteLock,它默認(rèn)情況 下是非公平鎖,但是可以設(shè)置為公平鎖。

    公平鎖即盡量以請(qǐng)求鎖的順序來獲取鎖。比如同是有多個(gè)線程在等待一個(gè) 鎖,當(dāng)這個(gè)鎖被釋放時(shí),等待時(shí)間最久的線程(最先請(qǐng)求的線程)會(huì)獲得該鎖, 這種就是公平鎖。

    4. 讀寫鎖

    正因?yàn)橛辛俗x寫鎖,才使得多個(gè)線程之間的讀操作不會(huì)發(fā)生沖突。 ReadWriteLock 就是讀寫鎖,它是一個(gè)接口,ReentrantReadWriteLock 實(shí) 現(xiàn)了這個(gè)接口。可以通過 readLock()獲取讀鎖,通過 writeLock()獲取寫鎖。

    九. 鎖優(yōu)化

    1. 自旋鎖

    • 為了讓線程等待,讓線程執(zhí)行一個(gè)忙循環(huán)(自旋)。需要物理機(jī)器有一個(gè)以 上的處理器。自旋等待雖然避免了線程切換的開銷,但它是要占用處理器時(shí)間 的,所以如果鎖被占用的時(shí)間很短,自旋等待的效果就會(huì)非常好,反之自旋的 線程只會(huì)白白消耗處理器資源。自旋次數(shù)的默認(rèn)值是 10 次,可以使用參數(shù) -XX:PreBlockSpin 來更改。
    • 自適應(yīng)自旋鎖:自旋的時(shí)間不再固定,而是由前一次在同一個(gè)鎖上的自旋 時(shí)間及鎖的擁有者的狀態(tài)來決定。

    2. 鎖清除

    • 指虛擬機(jī)即時(shí)編譯器在運(yùn)行時(shí),對(duì)一些代碼上要求同步,但是被檢測(cè)到不 可能存在共享數(shù)據(jù)競(jìng)爭(zhēng)的鎖進(jìn)行清除(逃逸分析技術(shù):在堆上的所有數(shù)據(jù)都不會(huì) 逃逸出去被其它線程訪問到,可以把它們當(dāng)成棧上數(shù)據(jù)對(duì)待)。

    3. 鎖粗化

    如果虛擬機(jī)探測(cè)到有一串零碎的操作都對(duì)同一個(gè)對(duì)象加鎖,將會(huì)把加鎖同 步的范圍擴(kuò)展到整個(gè)操作序列的外部。

    4. 輕量級(jí)鎖

    • 在代碼進(jìn)入同步塊時(shí),如果此同步對(duì)象沒有被鎖定,虛擬機(jī)首先將在當(dāng)前 線程的棧幀中建立一個(gè)名為鎖記錄(Lock Record)的空間,用于存儲(chǔ)所對(duì)象目 前的 Mark Word 的拷貝。然后虛擬機(jī)將使用 CAS 操作嘗試將對(duì)象的 Mark Word 更新為執(zhí)行 Lock Record 的指針。如果成功,那么這個(gè)線程就擁有了該 對(duì)象的鎖。如果更新操作失敗,虛擬機(jī)首先會(huì)檢查對(duì)象的 Mark Word 是否指 向當(dāng)前線程的棧幀,如果是就說明當(dāng)前線程已經(jīng)擁有了這個(gè)對(duì)象的鎖,否則說 明這個(gè)對(duì)象已經(jīng)被其它線程搶占。如果有兩條以上的線程爭(zhēng)用同一個(gè)鎖,那輕 量級(jí)鎖就不再有效,要膨脹為重量級(jí)鎖。
    • 解鎖過程:如果對(duì)象的 Mark Word 仍然指向著線程的鎖記錄,那就用 CAS 操作把對(duì)象當(dāng)前的 Mark Word 和和線程中復(fù)制的 Displaced Mark Word 替 換回來,如果替換成功,整個(gè)過程就完成。如果失敗,說明有其他線程嘗試過 獲取該鎖,那就要在釋放鎖的同時(shí),喚醒被掛起的線程。
    • 輕量級(jí)鎖的依據(jù):對(duì)于絕大部分的鎖,在整個(gè)同步周期內(nèi)都是不存在競(jìng)爭(zhēng) 的。
    • 傳統(tǒng)鎖(重量級(jí)鎖)使用操作系統(tǒng)互斥量來實(shí)現(xiàn)的。

    HotSpot 虛擬機(jī)的對(duì)象的內(nèi)存布局:對(duì)象頭(Object Header)分為兩部分信息嗎,第 一部分(Mark Word)用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù),另一個(gè)部分用于存儲(chǔ)指向方法區(qū) 對(duì)象數(shù)據(jù)類型的指針,如果是數(shù)組的話,還會(huì)由一個(gè)額外的部分用于存儲(chǔ)數(shù)組的長度。

    32 位 HotSpot 虛擬機(jī)中對(duì)象未被鎖定的狀態(tài)下, Mark Word 的 32 個(gè) Bits 空間中 25 位 用于存儲(chǔ)對(duì)象哈希碼,4 位存儲(chǔ)對(duì)象分代年齡,2 位存儲(chǔ)鎖標(biāo)志位,1 位固定為 0。

    HotSpot 虛擬機(jī)對(duì)象頭 Mark Word

    存儲(chǔ)內(nèi)容標(biāo)志位狀態(tài)對(duì)象哈希碼、對(duì)象分代年齡01未鎖定指向鎖記錄的指針00輕量級(jí)鎖定指向重量級(jí)鎖的指針10膨脹(重量級(jí)鎖)空,不記錄信息11GC 標(biāo)記偏向線程 ID,偏向時(shí)間戳、對(duì)象分代年齡01可偏向

    5. 偏向鎖

    • 目的是消除在無競(jìng)爭(zhēng)情況下的同步原語,進(jìn)一步提高程序的運(yùn)行性能。鎖 會(huì)偏向第一個(gè)獲得它的線程,如果在接下來的執(zhí)行過程中,該鎖沒有被其它線 程獲取,則持有鎖的線程將永遠(yuǎn)不需要再進(jìn)行同步。
    • 當(dāng)鎖第一次被線程獲取的時(shí)候,虛擬機(jī)將會(huì)把對(duì)象頭中的標(biāo)志位設(shè)為 01, 同時(shí)使用 CAS 操作把獲取到這個(gè)鎖的線程的 ID 記錄在對(duì)象的 Mark Word 之 中,如果成功,持有偏向鎖的線程以后每次進(jìn)入這個(gè)鎖相關(guān)的同步塊時(shí),都可 以不進(jìn)行任何同步操作。
    • 當(dāng)有另一個(gè)線程去嘗試獲取這個(gè)鎖時(shí),偏向模式就宣告結(jié)束。根據(jù)鎖對(duì)象 目前是否處于被鎖定的狀態(tài),撤銷偏向后恢復(fù)到未鎖定或輕量級(jí)鎖定狀態(tài)。

    十. wait()和 sleep()的區(qū)別。

  • 這兩個(gè)方法來自不同的類,sleep()來自 Thread 類,是靜態(tài)方法;wait() 是 Object 類里面的方法,和 notify()或者 notifyAll()方法配套使用,來實(shí)現(xiàn) 線程間的通信。
  • 最主要是 sleep 是將當(dāng)前線程掛起指定的時(shí)間,沒有釋放鎖;而 wait 方法 釋放了鎖,使得其他線程可以使用同步控制塊或者方法。
  • 使用范圍:wait,notify 和 notifyAll 只能在同步控制方法或者同步控制塊 里面使用,而 sleep 可以在任何地方使用 。
  • synchronized(x){ x.notify() //或者 wait()}

    特別注意: sleep 和 wait 必須捕獲異常(Thread.sleep()和 Object.wait() 都會(huì)拋出 InterruptedException), notify 和 notifyAll 不需要捕獲異常。

    十一. Java 中 interrupted() 和 isInterrupted()方法的區(qū)別?

    二個(gè)方法都是判斷線程是否停止的方法。

  • 前者是靜態(tài)方法,后者是非靜態(tài)方法。interrupted 是作用于當(dāng)前正在運(yùn) 行的線程, isInterrupted 是作用于調(diào)用該方法的線程對(duì)象所對(duì)應(yīng)的線程。 (線程對(duì)象對(duì)應(yīng)的線程不一定是當(dāng)前運(yùn)行的線程。例如我們可以在 A 線程中去調(diào)用 B 線程對(duì)象的 isInterrupted 方法,此時(shí),當(dāng)前正在運(yùn)行的線程就是 A 線程。)
  • 前者會(huì)將中斷狀態(tài)清除而后者不會(huì)。
  • 十二. Java 創(chuàng)建線程之后,直接調(diào)用 start()方法和 run()的區(qū)別 ?

  • start()方法來啟動(dòng)線程,并在新線程中運(yùn)行 run()方法,真正實(shí)現(xiàn)了 多線程運(yùn)行。這時(shí)無需等待 run 方法體代碼執(zhí)行完畢,可以直接繼續(xù)執(zhí)行下面 的代碼;通過調(diào)用 Thread 類的 start()方法來啟動(dòng)一個(gè)線程,這時(shí)此線程是 處于就緒狀態(tài),并沒有運(yùn)行,然后通過此 Thread 類調(diào)用方法 run()來完成其 運(yùn)行操作,這里方法 run()稱為線程體,它包含了要執(zhí)行的這個(gè)線程的內(nèi)容, run ()方法運(yùn)行結(jié)束,此線程終止。然后 CPU 再調(diào)度其它線程。
  • 直接調(diào)用 run()方法的話,會(huì)把 run()方法當(dāng)作普通方法來調(diào)用,會(huì) 在當(dāng)前線程中執(zhí)行 run()方法,而不會(huì)啟動(dòng)新線程來運(yùn)行 run()方法。程序還 是要順序執(zhí)行, 要等待 run 方法體執(zhí)行完畢后,才可繼續(xù)執(zhí)行下面的代碼; 程 序中只有主線程——這一個(gè)線程, 其程序執(zhí)行路徑還是只有一條, 這樣就沒有 達(dá)到多線程的目的。
  • 十三. 什么是線程的上下文切換?

    對(duì)于單核 CPU,CPU 在一個(gè)時(shí)刻只能運(yùn)行一個(gè)線程,當(dāng)在運(yùn)行一個(gè)線程的 過程中轉(zhuǎn)去運(yùn)行另外一個(gè)線程,這個(gè)叫做線程上下文切換(對(duì)于進(jìn)程也是類似)。

    線程上下文切換過程中會(huì)記錄程序計(jì)數(shù)器、CPU 寄存器的狀態(tài)等數(shù)據(jù)。

    雖然多線程可以使得任務(wù)執(zhí)行的效率得到提升,但是由于在線程切換時(shí)同 樣會(huì)帶來一定的開銷代價(jià),并且多個(gè)線程會(huì)導(dǎo)致系統(tǒng)資源占用的增加,所以在 進(jìn)行多線程編程時(shí)要注意這些因素。

    十四. 怎么檢測(cè)一個(gè)線程是否擁有鎖?

    在 java.lang.Thread 中有一個(gè)方法叫 holdsLock(Object obj),它返回 true,如果當(dāng)且僅當(dāng)當(dāng)前線程擁有某個(gè)具體對(duì)象的鎖。

    十五. 用戶線程和守護(hù)線程有什么區(qū)別?

    當(dāng)我們?cè)?Java 程序中創(chuàng)建一個(gè)線程,它就被稱為用戶線程。將一個(gè)用戶線 程設(shè)置為守護(hù)線程的方法就是在調(diào)用start()方法之前, 調(diào)用對(duì)象的 setDamon(true)方法。一個(gè)守護(hù)線程是在后臺(tái)執(zhí)行并且不會(huì)阻止 JVM 終止的 線程,守護(hù)線程的作用是為其他線程的運(yùn)行提供便利服務(wù)。當(dāng)沒有用戶線程在 運(yùn)行的時(shí)候, JVM 關(guān)閉程序并且退出。一個(gè)守護(hù)線程創(chuàng)建的子線程依然是守護(hù) 線程。

    守護(hù)線程的一個(gè)典型例子就是垃圾回收器。

    十六. 什么是線程調(diào)度器?

    線程調(diào)度器是一個(gè)操作系統(tǒng)服務(wù),它負(fù)責(zé)為 Runnable 狀態(tài)的線程分配 CPU 時(shí)間。一旦我們創(chuàng)建一個(gè)線程并啟動(dòng)它,它的執(zhí)行便依賴于線程調(diào)度器的 實(shí)現(xiàn)。

    十七. 線程的狀態(tài)。

    版本 1.

    在 Java 當(dāng)中,線程通常都有五種狀態(tài),創(chuàng)建、就緒、運(yùn)行、阻塞和死亡。

    • 第一是創(chuàng)建狀態(tài)。在生成線程對(duì)象,并沒有調(diào)用該對(duì)象的 start 方法,這是 線程處于創(chuàng)建狀態(tài)。
    • 第二是就緒狀態(tài)。當(dāng)調(diào)用了線程對(duì)象的 start 方法之后,該線程就進(jìn)入了就 緒狀態(tài),但是此時(shí)線程調(diào)度程序還沒有把該線程設(shè)置為當(dāng)前線程,此時(shí)處于就 緒狀態(tài)。在線程運(yùn)行之后,從等待或者睡眠中回來之后,也會(huì)處于就緒狀態(tài)。
    • 第三是運(yùn)行狀態(tài)。線程調(diào)度程序?qū)⑻幱诰途w狀態(tài)的線程設(shè)置為當(dāng)前線程, 此時(shí)線程就進(jìn)入了運(yùn)行狀態(tài),開始運(yùn)行 run 函數(shù)當(dāng)中的代碼。
    • 第四是阻塞狀態(tài)。線程正在運(yùn)行的時(shí)候,被暫停,通常是為了等待某個(gè)事 件的發(fā)生(比如說某項(xiàng)資源就緒)之后再繼續(xù)運(yùn)行。sleep,wait 等方法都可以導(dǎo) 致線程阻塞。
    • 第五是死亡狀態(tài)。如果一個(gè)線程的 run 方法執(zhí)行結(jié)束或者異常中斷后,該 線程就會(huì)死亡。對(duì)于已經(jīng)死亡的線程,無法再使用 start 方法令其進(jìn)入就緒。

    版本 2.

    一般來說,線程包括以下這幾個(gè)狀態(tài):創(chuàng)建(new)、就緒(runnable)、運(yùn) 行(running)、阻塞(blocked)、timed_waiting、waiting、消亡(dead)。

    十八. 有三個(gè)線程 T1,T2,T3,怎么確保它們按順序執(zhí)行?

    join()方法。

    十九. 在一個(gè)主線程中,要求有大量子線程執(zhí)行完之后,主線程才執(zhí)行完成。多種方式,考慮效率。

    1. 在主函數(shù)中使用 join()方法。

    t1.start();t2.start();t3.start();t1.join(); //不會(huì)導(dǎo)致 t1 和 t2 和 t3 的順序執(zhí)行 t2.join();t3.join();System.out.println("Main finished");

    2. CountDownLatch,一個(gè)同步輔助類,在完成一組正在其他線程中執(zhí)行 的操作之前,它允許一個(gè)或多個(gè)線程一直等待。

    public class WithLatch { public static void main(String[] args) { CountDownLatch latch = new CountDownLatch(3); for (int i = 0; i < 3; i++) { new ChildThead(i, latch).start(); } try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Main finished"); } static class ChildThead extends Thread { private int id = -1; private CountDownLatch latch = null; public ChildThead(int id, CountDownLatch latch) { this.id = id; this.latch = latch; } public void run() { try { Thread.sleep(Math.abs(new Random().nextint(5000))); System.out.println(String.format("Child Thread %dfinished

    總結(jié)

    以上是生活随笔為你收集整理的多个线程访问统一对象的不同方法_不会多线程还想进BAT?精选19道多线程面试题,有答案边看边学...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。