java面經4-線程

線程

1、多線程中的i++線程安全嗎?爲什麼?
若i是局部變量則是安全的
若是全局變量則不安全,i++,不是原子操作。
2、如何線程安全的實現一個計數器?
使用AtomicInteger
使用Synchronized關鍵字;
3、多線程同步的方法
Synchroinzed關鍵字、Lock、volatile、ThreadLocal
4、介紹一下生產者消費者模式?
組成:緩衝區、消費者、生產者
操作:消費者必須在緩衝區>0的時候纔可以消費、生產者必須在緩衝區小於緩衝區最大存儲數的時候纔可以生產
優點:1、解耦;2、異步;3、削峯。當消費者的消費速度更不是生產者的時候
5、線程,進程,然後線程創建有很大開銷,怎麼優化?
使用線程池,避免線程頻繁的被創建和銷燬,重用線程。
6、線程池運行流程,參數,策略
參數:核心線程數、最大線程數、同步隊列、拒絕策略
流程:當來到一個任務,若存在空閒的線程,則使用該線程執行任務。若不存在空閒線程,當前線程數小於核心線程數時,會新建一個線程來執行任務;若當前線程數大於等於核心線程數,會將任務加入同步隊列。若同步隊列未滿,則添加成功;否則若當前線程數小於最大線程數時,會創建線程來執行任務;若當前線程數大於等於最大線程數時,則會拋出異常,交由異常處理方法來進行操作。
7、講一下AQS吧。
AbstractQueueSynchronized 是一個抽象類
抽象隊列式同步器
state狀態:volatile int類型的變量,用戶表示當前同步狀態

根據自己同步器需要滿足的性質實現線程獲取和釋放資源的方式(修改同步狀態變量的方式)即可,至於具體線程等待隊列的維護(如獲取資源失敗入隊、喚醒出隊、以及線程在隊列中行爲的管理等),AQS在其頂層已經幫我們實現好了,AQS的這種設計使用的正是模板方法模式

8、創建線程的方法,哪個更好,爲什麼?
實現Runnable接口。
採用繼承Thread類方式:
(1)優點:編寫簡單,如果需要訪問當前線程,無需使用Thread.currentThread()方法,直接使用this,即可獲得當前線程。
(2)缺點:因爲線程類已經繼承了Thread類,所以不能再繼承其他的父類。
採用實現Runnable接口方式:
(1)優點:線程類只是實現了Runable接口,還可以繼承其他的類。在這種方式下,可以多個線程共享同一個目標對象,所以非常適合多個相同線程來處理同一份資源的情況,從而可以將CPU代碼和數據分開,形成清晰的模型,較好地體現了面向對象的思想。
(2)缺點:編程稍微複雜,如果需要訪問當前線程,必須使用Thread.currentThread()方法。

10、Java中有幾種線程池?
(1) newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程

return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      				60L, TimeUnit.SECONDS,
                                      				new SynchronousQueue<Runnable>());

(2) newFixedThreadPool創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }

(3) newScheduledThreadPool創建一個定長線程池,支持定時和週期性任務執行(DelayedWorkQueue)

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            for (;;) {
                E first = q.peek();
                if (first == null) {
                    if (nanos <= 0)
                        return null;
                    else
                        nanos = available.awaitNanos(nanos);
                } else {
                    long delay = first.getDelay(TimeUnit.NANOSECONDS);
                    if (delay > 0) {
                        if (nanos <= 0)
                            return null;
                        if (delay > nanos)
                            delay = nanos;
                        long timeLeft = available.awaitNanos(delay);
                        nanos -= delay - timeLeft;
                    } else {
                        E x = q.poll();
                        assert x != null;
                        if (q.size() != 0)
                            available.signalAll();
                        return x;
                    }
                }
            }
        } finally {
            lock.unlock();
        }
    }

(4) newSingleThreadExecutor創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO,LIFO,優先級)執行。

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

11、線程池有什麼好處?
1)線程的重用
2)控制線程池的併發數
3)管理線程(定時、定期、單線程、併發數控制)
12、cyclicbarrier和countdownlatch的區別
cyclicbarrier: N個線程相互等待,任何一個線程完成之前,所有的線程都必須等待,可重用
countdownlatch:一個線程(或者多個), 等待另外N個線程完成某個事情之後才能執行,不可重用
13、如何理解Java多線程回調方法?
Future?
15、概括的解釋下線程的幾種可用狀態。
New、Blocked、Runnable、Running、Dead
16、同步方法和同步代碼塊的區別是什麼?
同步方法:(粗粒度鎖):
1.修飾一般方法: public synchronized void method (){…},獲取的是當前調用對象this上的鎖
2.修飾靜態方法: public static synchronized void method (){…},獲取當前類的對象上的鎖
同步代碼塊(細粒度鎖):
synchronized ( obj ) {…},同步代碼塊可以指定獲取哪個對象上的鎖

17、啓動線程有哪幾種方式,線程池有哪幾種?
要啓動的可以分爲兩類:返回結果和不返回結果。對於這兩種,也分別有兩種啓動線程的方式:
1)繼承Thread類,implements Runnable接口
2)實現Callable接口通過FutureTask包裝器來創建Thread線程、使用ExecutorService、Callable、Future實現有返回結果的線程

18、在監視器(Monitor)內部,是如何做線程同步的?程序應該做哪種級別的同步?
在 java 虛擬機中, 每個對象( Object 和 class )通過某種邏輯關聯監視器,每個監視器和一個對象引用相關聯, 爲了實現監視器的互斥功能, 每個對象都關聯着一把鎖.
一旦方法或者代碼塊被 synchronized 修飾, 那麼這個部分就放入了監視器的監視區域, 確保一次只能有一個線程執行該部分的代碼, 線程在獲取鎖之前不允許執行該部分的代碼

另外 java 還提供了顯式監視器( Lock )和隱式監視器( synchronized )兩種鎖方案

19、sleep() 和 wait() 有什麼區別?

sleep() wait()
不釋放鎖、讓出CPU wait():釋放鎖、讓出CPU
任何地方使用 只能在同步方法或同步塊中使用
是線程類的方法 wait()是Object的方法

20、同步和異步有何異同,在什麼情況下分別使用他們?舉例說明。

同步:指發送一個請求,需要等待返回,然後才能夠發送下一個請求,有個等待過程;
異步:指發送一個請求,不需要等待返回,隨時可以再發送下一個請求,即不需要等待。
區別:一個需要等待,一個不需要等待,
在部分情況下,我們的項目開發中都會優先選擇不需要等待的異步交互方式。
哪些情況建議使用同步交互呢?比如銀行的轉賬系統,對數據庫的保存操作等等,都會使用同步交互操作,其餘情況都優先使用異步交互。

21、設計4個線程,其中兩個線程每次對j增加1,另外兩個線程對j每次減少1。使用內部類實現線程,對j增減的時候沒有考慮順序問題。

22、啓動一個線程是用run()還是start()?
start()
23、請說出你所知道的線程同步的方法
synchronized、Lock、volatile、ThreadLocal、使用原子類
24、多線程有幾種實現方法,都是什麼?同步有幾種實現方法,都是什麼?

25、java中有幾種方法可以實現一個線程?用什麼關鍵字修飾同步方法? stop()和suspend()方法爲何不推薦使用?
反對使用stop(),是因爲它不安全。它會解除由線程獲取的所有鎖定,當在一個線程對象上調用stop()方法時,這個線程對象所運行的線程就會立即停止,假如一個線程正在執行:synchronized void { x = 3; y = 4;} 由於方法是同步的,多個線程訪問時總能保證x,y被同時賦值,而如果一個線程正在執行到x = 3;時,被調用了 stop()方法,即使在同步塊中,它也乾脆地stop了,這樣就產生了不完整的殘廢數據。而多線程編程中最最基礎的條件要保證數據的完整性,所以請忘記線程的stop方法,以後我們再也不要說“停止線程”了。而且如果對象處於一種不連貫狀態,那麼其他線程能在那種狀態下檢查和修改它們。結果很難檢查出真正的問題所在。
suspend()方法容易發生死鎖。調用suspend()的時候,目標線程會停下來,但卻仍然持有在這之前獲得的鎖定。此時,其他任何線程都不能訪問鎖定的資源,除非被"掛起"的線程恢復運行。對任何線程來說,如果它們想恢復目標線程,同時又試圖使用任何一個鎖定的資源,就會造成死鎖。所以不應該使用suspend(),而應在自己的Thread類中置入一個標誌,指出線程應該活動還是掛起。若標誌指出線程應該掛起,便用 wait()命其進入等待狀態。若標誌指出線程應當恢復,則用一個notify()重新啓動線程。
26、線程的sleep()方法和yield()方法有什麼區別?
yield()的作用是讓步。它能讓當前線程由“運行狀態”進入到“就緒狀態”,從而讓其它具有相同優先級的等待線程獲取執行權;但是,並不能保證在當前線程調用yield()之後,其它具有相同優先級的線程就一定能獲得執行權;也有可能是當前線程又進入到“運行狀態”繼續運行
27、當一個線程進入一個對象的synchronized方法A之後,其它線程是否可進入此對象的synchronized方法B?
不可以。方法A爲成員方法。
28、請說出與線程同步以及線程調度相關的方法。
線程同步就是當一個線程對內存進行操作時,其他線程不可以對這個內存地址進行操作,其他線程只能是等待狀態。
線程調度是按照特定的機制爲多個線程分配cpu使用權;
wait()使一個線程處於等待狀態,並且釋放所持對象的鎖;
sleep()使一個正在運行的線程處於睡眠狀態,是一個靜態方法,調用此方法會拋出InterruptException異常
notify()喚醒一個處於等待狀態的線程,調用此方法不能確定喚醒哪一個線程 又jvm確定並且與優先級無關
notifyall()喚醒所有進行等待狀態的線程,該方法不是將對象的鎖給所有線程 而是讓他們競爭,獲得鎖的線程進入就緒狀態。
29、舉例說明同步和異步

30、什麼是線程池(thread pool)?

31、說說線程的基本狀態以及狀態之間的關係?

32、如何保證線程安全?
原子性——鎖
這一點,跟數據庫事務的原子性概念差不多,即一個操作(有可能包含有多個子操作)要麼全部執行(生效),要麼全部都不執行(都不生效)。
可見性——volatile+鎖
可見性是指,當多個線程併發訪問共享變量時,一個線程對共享變量的修改,其它線程能夠立即看到。可見性問題是好多人忽略或者理解錯誤的一點。
CPU從主內存中讀數據的效率相對來說不高,現在主流的計算機中,都有幾級緩存。每個線程讀取共享變量時,都會將該變量加載進其對應CPU的高速緩存裏,修改該變量後,CPU會立即更新該緩存,但並不一定會立即將其寫回主內存(實際上寫回主內存的時間不可預期)。此時其它線程(尤其是不在同一個CPU上執行的線程)訪問該變量時,從主內存中讀到的就是舊的數據,而非第一個線程更新後的數據。
這一點是操作系統或者說是硬件層面的機制,所以很多應用開發人員經常會忽略。
順序性——volatile+鎖
順序性指的是,程序執行的順序按照代碼的先後順序執行

33、爲什麼採用雙親委派模型
避免重複加載 + 避免核心類篡改。
採用雙親委派模式的是好處是Java類隨着它的類加載器一起具備了一種帶有優先級的層次關係,通過這種層級關可以避免類的重複加載,當父親已經加載了該類時,就沒有必要子ClassLoader再加載一次。其次是考慮到安全因素,java核心api中定義類型不會被隨意替換,假設通過網絡傳遞一個名爲java.lang.Integer的類,通過雙親委託模式傳遞到啓動類加載器,而啓動類加載器在覈心Java API發現這個名字的類,發現該類已被加載,並不會重新加載網絡傳遞的過來的java.lang.Integer,而直接返回已加載過的Integer.class,這樣便可以防止核心API庫被隨意篡改。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章