Java多線程問題(下)

2一、FutureTask是什麼

這個其實前面有提到過,FutureTask表示一個異步運算的任務。FutureTask裏面能夠傳入一個Callable的具體實現類,能夠對這個異步運算的任務的結果進行等待獲取、判斷是否已經完成、取消任務等操做。固然,因爲FutureTask也是Runnable接口的實現類,因此FutureTask也能夠放入線程池中。咱們知道在Java的新版中中引入了AIO模型,相對於以前的NIO,AIO編程提供了異步模型,若是對IO模型有疑惑,點這裏:http://my.oschina.net/u/2288283/blog/625932.  在AIO編程過程當中就會用到這個FutureTask,具體的用法我會在從此的博客中介紹一下。html

2二、Linux環境下如何查找哪一個線程使用CPU最長

這是一個比較偏實踐的問題,這種問題我以爲挺有意義的。能夠這麼作:java

獲取項目的pid,jps或者ps -ef | grep java,這個前面有講過算法

2三、Java編程寫一個會致使死鎖的程序

第一次看到這個題目,以爲這是一個很是好的問題。不少人都知道死鎖是怎麼一回事兒:線程A和線程B相互等待對方持有的鎖致使程序無限死循環下去。固然也僅限於此了,問一下怎麼寫一個死鎖的程序就不知道了,這種狀況說白了就是不懂什麼是死鎖,懂一個理論就完事兒了,實踐中碰到死鎖的問題基本上是看不出來的。sql

真正理解什麼是死鎖,這個問題其實不難,幾個步驟:數據庫

(1)兩個線程裏面分別持有兩個Object對象:lock1和lock2。這兩個lock做爲同步代碼塊的鎖;編程

(2)線程1的run()方法中同步代碼塊先獲取lock1的對象鎖,Thread.sleep(xxx),時間不須要太多,50毫秒差很少了,而後接着獲取lock2的對象鎖。這麼作主要是爲了防止線程1啓動一會兒就連續得到了lock1和lock2兩個對象的對象鎖緩存

(3)線程2的run()方法中同步代碼塊先獲取lock2的對象鎖,接着獲取lock1的對象鎖,固然這時lock1的對象鎖已經被線程1鎖,線程2確定是要等待線程1釋放lock1的對象鎖的安全

下面演示一個死鎖的實例:服務器

public class DeadLock {

    public static final Object lock1=new Object();
    public static final Object lock2=new Object();



    public static void main(String[] args){
        Thread thread1=new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock1){
                    try {
                        Thread.sleep(1000);
                        synchronized (lock2){
                            System.out.println("線程1以得到鎖1,正在得到鎖2");
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        Thread thread2=new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock2){
                    try {
                        Thread.sleep(1000);
                        synchronized (lock1){
                            System.out.println("線程2以得到鎖2,正在嘗試得到鎖1");
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        thread1.start();
        thread2.start();
    }

}



2四、怎麼喚醒一個阻塞的線程

若是線程是由於調用了wait()、sleep()或者join()方法而致使的阻塞,能夠中斷線程,而且經過拋出InterruptedException來喚醒它;若是線程遇到了IO阻塞,無能爲力,由於IO是操做系統實現的,Java代碼並無辦法直接接觸到操做系統。多線程

2五、不可變對象對多線程有什麼幫助

前面有提到過的一個問題,不可變對象保證了對象的內存可見性,對不可變對象的讀取不須要進行額外的同步手段,提高了代碼執行效率。好比String對象,在不一樣的線程中依然能夠「保持自我」,多麼清高的String阿。。巴拉巴拉


2六、什麼是多線程的上下文切換

多線程的上下文切換是指CPU控制權由一個已經正在運行的線程切換到另一個就緒並等待獲取CPU執行權的線程的過程。

2七、若是你提交任務時,線程池隊列已滿,這時會發生什麼

若是你使用的LinkedBlockingQueue,也就是無界隊列的話,不要緊,繼續添加任務到阻塞隊列中等待執行,由於LinkedBlockingQueue能夠近乎認爲是一個無窮大的隊列,能夠無限存聽任務;若是你使用的是有界隊列比方說ArrayBlockingQueue的話,任務首先會被添加到ArrayBlockingQueue中,ArrayBlockingQueue滿了,則會使用拒絕策略RejectedExecutionHandler處理滿了的任務,默認是AbortPolicy

2八、Java中用到的線程調度算法是什麼

搶佔式。一個線程用完CPU以後,操做系統會根據線程優先級、線程飢餓狀況等數據算出一個總的優先級並分配下一個時間片給某個線程執行。

2九、Thread.sleep(0)的做用是什麼

這個問題和上面那個問題是相關的,我就連在一塊兒了。因爲Java採用搶佔式的線程調度算法,所以可能會出現某條線程經常獲取到CPU控制權的狀況,爲了讓某些優先級比較低的線程也能獲取到CPU控制權,可使用Thread.sleep(0)手動觸發一次操做系統分配時間片的操做,這也是平衡CPU控制權的一種操做。

30、什麼是自旋

不少synchronized裏面的代碼只是一些很簡單的代碼,執行時間很是快,此時等待的線程都加鎖多是一種不太值得的操做,由於線程阻塞涉及到用戶態和內核態切換的問題。既然synchronized裏面的代碼執行得很是快,不妨讓等待鎖的線程不要被阻塞,而是在synchronized的邊界作忙循環,這就是自旋。若是作了屢次忙循環發現尚未得到鎖,再阻塞,這樣多是一種更好的策略。詳情見:http://my.oschina.net/u/2288283/blog/638969


3一、什麼是Java內存模型

Java內存模型定義了一種多線程訪問Java內存的規範。Java內存模型要完整講不是這裏幾句話能說清楚的,我簡單總結一下Java內存模型的幾部份內容:

(1)Java內存模型將內存分爲了主內存和工做內存。類的狀態,也就是類之間共享的變量,是存儲在主內存中的,每次Java線程用到這些主內存中的變量的時候,會讀一次主內存中的變量,並讓這些內存在本身的工做內存中有一份拷貝,運行本身線程代碼的時候,用到這些變量,操做的都是本身工做內存中的那一份。在線程代碼執行完畢以後,會將最新的值更新到主內存中去

(2)定義了幾個原子操做,用於操做主內存和工做內存中的變量

(3)定義了volatile變量的使用規則

(4)happens-before,即先行發生原則,定義了操做A必然先行發生於操做B的一些規則,好比在同一個線程內控制流前面的代碼必定先行發生於控制流後面的代碼、一個釋放鎖unlock的動做必定先行發生於後面對於同一個鎖進行鎖定lock的動做等等,只要符合這些規則,則不須要額外作同步措施,若是某段代碼不符合全部的happens-before規則,則這段代碼必定是線程非安全的

3二、什麼是CAS

CAS,全稱爲Compare and Swap,即比較-替換。假設有三個操做數:內存值V、舊的預期值A、要修改的值B,當且僅當預期值A和內存值V相同時,纔會將內存值修改成B並返回true,不然什麼都不作並返回false。固然CAS必定要volatile變量配合,這樣才能保證每次拿到的變量是主內存中最新的那個值,不然舊的預期值A對某條線程來講,永遠是一個不會變的值A,只要某次CAS操做失敗,永遠都不可能成功。在Java的鎖機制內部不少都是借用了CAS鎖來實現,固然CAS仍是要靠硬件支持滴。

3三、什麼是樂觀鎖和悲觀鎖

(1)樂觀鎖:就像它的名字同樣,對於併發間操做產生的線程安全問題持樂觀狀態,樂觀鎖認爲競爭不老是會發生,所以它不須要持有鎖,將比較-替換這兩個動做做爲一個原子操做嘗試去修改內存中的變量,若是失敗則表示發生衝突,那麼就應該有相應的重試邏輯。

(2)悲觀鎖:仍是像它的名字同樣,對於併發間操做產生的線程安全問題持悲觀狀態,悲觀鎖認爲競爭老是會發生,所以每次對某資源進行操做時,都會持有一個獨佔的鎖,就像synchronized,無論三七二十一,直接上了鎖就操做資源了。

說點題外話,其實樂觀鎖和悲觀鎖的問題在數據庫中也很常見,好比Mysql的鎖問題,InnoDB默認的就是靠MVCC機制實現的樂觀鎖,悲觀鎖的話能夠參考行鎖,表鎖等等來理解。

3四、什麼是AQS

簡單說一下AQS,AQS全稱爲AbstractQueuedSychronizer,翻譯過來應該是抽象隊列同步器。

若是說java.util.concurrent的基礎是CAS的話,那麼AQS就是整個Java併發包的核心了,ReentrantLock、CountDownLatch、Semaphore等等都用到了它。AQS實際上以雙向隊列的形式鏈接全部的Entry,比方說ReentrantLock,全部等待的線程都被放在一個Entry中並連成雙向隊列,前面一個線程使用ReentrantLock好了,則雙向隊列實際上的第一個Entry開始運行。

AQS定義了對雙向隊列全部的操做,而只開放了tryLock和tryRelease方法給開發者使用,開發者能夠根據本身的實現重寫tryLock和tryRelease方法,以實現本身的併發功能。

3五、單例模式的線程安全性

老生常談的問題了,首先要說的是單例模式的線程安全意味着:某個類的實例在多線程環境下只會被建立一次出來。單例模式有不少種的寫法,我總結一下:

(1)餓漢式單例模式的寫法:線程安全

(2)懶漢式單例模式的寫法:非線程安全

(3)雙檢鎖單例模式的寫法:線程安全

不過對於單例的解釋這裏也只是一個很是粗略的,若是有興趣的網友能夠網上自行查找:如何安全的建立單例模式,可使用volatile呦。。

3六、Semaphore有什麼做用

Semaphore就是一個信號量,它的做用是限制某段代碼塊的併發數。Semaphore有一個構造函數,能夠傳入一個int型整數n,表示某段代碼最多隻有n個線程能夠訪問,若是超出了n,那麼請等待,等到某個線程執行完畢這段代碼塊,下一個線程再進入。由此能夠看出若是Semaphore構造函數中傳入的int型整數n=1,至關於變成了一個synchronized了。

3七、Hashtable的size()方法中明明只有一條語句」return count」,爲何還要作同步?

這是我以前的一個困惑,不知道你們有沒有想過這個問題。某個方法中若是有多條語句,而且都在操做同一個類變量,那麼在多線程環境下不加鎖,勢必會引起線程安全問題,這很好理解,可是size()方法明明只有一條語句,爲何還要加鎖?

關於這個問題,在慢慢地工做、學習中,有了理解,主要緣由有兩點:

(1)同一時間只能有一條線程執行固定類的同步方法,可是對於類的非同步方法,能夠多條線程同時訪問。因此,這樣就有問題了,可能線程A在執行Hashtable的put方法添加數據,線程B則能夠正常調用size()方法讀取Hashtable中當前元素的個數,那讀取到的值可能不是最新的,可能線程A添加了完了數據,可是沒有對size++,線程B就已經讀取size了,那麼對於線程B來講讀取到的size必定是不許確的。而給size()方法加了同步以後,意味着線程B調用size()方法只有在線程A調用put方法完畢以後才能夠調用,這樣就保證了線程安全性

(2)CPU執行代碼,執行的不是Java代碼,這點很關鍵,必定得記住。Java代碼最終是被翻譯成彙編代碼執行的,彙編代碼纔是真正能夠和硬件電路交互的代碼。即便你看到Java代碼只有一行,甚至你看到Java代碼編譯以後生成的字節碼也只有一行,也不意味着對於底層來講這句語句的操做只有一個。一句」return count」假設被翻譯成了三句彙編語句執行,徹底可能執行完第一句,線程就切換了。

3八、線程類的構造方法、靜態塊是被哪一個線程調用的

這是一個很是刁鑽和狡猾的問題。請記住:線程類的構造方法、靜態塊是被new這個線程類所在的線程所調用的,而run方法裏面的代碼纔是被線程自身所調用的。

若是說上面的說法讓你感到困惑,那麼我舉個例子,假設Thread2中new了Thread1,main函數中new了Thread2,那麼:

(1)Thread2的構造方法、靜態塊是main線程調用的,Thread2的run()方法是Thread2本身調用的

(2)Thread1的構造方法、靜態塊是Thread2調用的,Thread1的run()方法是Thread1本身調用的

3九、同步方法和同步塊,哪一個是更好的選擇

同步塊,這意味着同步塊以外的代碼是異步執行的,這比同步整個方法更提高代碼的效率。請知道一條原則:同步的範圍越小越好

藉着這一條,我額外提一點,雖然說同步的範圍越少越好,可是在Java虛擬機中仍是存在着一種叫作鎖粗化的優化方法,這種方法就是把同步範圍變大。這是有用的,比方說StringBuffer,它是一個線程安全的類,天然最經常使用的append()方法是一個同步方法,咱們寫代碼的時候會反覆append字符串,這意味着要進行反覆的加鎖->解鎖,這對性能不利,由於這意味着Java虛擬機在這條線程上要反覆地在內核態和用戶態之間進行切換,所以Java虛擬機會將屢次append方法調用的代碼進行一個鎖粗化的操做,將屢次的append的操做擴展到append方法的頭尾,變成一個大的同步塊,這樣就減小了加鎖–>解鎖的次數,有效地提高了代碼執行的效率。

40、高併發、任務執行時間短的業務怎樣使用線程池?併發不高、任務執行時間長的業務怎樣使用線程池?併發高、業務執行時間長的業務怎樣使用線程池?

這是我在併發編程網上看到的一個問題,把這個問題放在最後一個,但願每一個人都能看到而且思考一下,由於這個問題很是好、很是實際、很是專業。關於這個問題,我的見解是:

(1)高併發、任務執行時間短的業務,線程池線程數能夠設置爲CPU核數+1,減小線程上下文的切換

(2)併發不高、任務執行時間長的業務要區分開看:

a)假如是業務時間長集中在IO操做上,也就是IO密集型的任務,由於IO操做並不佔用CPU,因此不要讓全部的CPU閒下來,能夠加大線程池中的線程數目,讓CPU處理更多的業務

b)假如是業務時間長集中在計算操做上,也就是計算密集型任務,這個就沒辦法了,和(1)同樣吧,線程池中的線程數設置得少一些,減小線程上下文的切換

(3)併發高、業務執行時間長,解決這種類型任務的關鍵不在於線程池而在於總體架構的設計,看看這些業務裏面某些數據是否能作緩存是第一步,增長服務器是第二步,至於線程池的設置,設置參考(2)。最後,業務執行時間長的問題,也可能須要分析一下,看看能不能使用中間件對任務進行拆分和解耦。

=====================================================================

以上的問題都是參考了其餘的博文,固然也加入了本身的一些觀點,若是問題,歡迎指正。

參考博文:http://www.importnew.com/18459.html

相關文章
相關標籤/搜索