想進大廠?50個多線程面試題,你會多少?【後25題】(二)

最近看到網上流傳着,各類面試經驗及面試題,每每都是一大堆技術題目貼上去,而沒有答案。html

無論你是新程序員仍是老手,你必定在面試中遇到過有關線程的問題。Java語言一個重要的特色就是內置了對併發的支持,讓Java大受企業和程序員的歡迎。大多數待遇豐厚的Java開發職位都要求開發者精通多線程技術而且有豐富的Java程序開發、調試、優化經驗,因此線程相關的問題在面試中常常會被提到。
在典型的Java面試中, 面試官會從線程的基本概念問起java

如:爲何你須要使用線程, 如何建立線程,用什麼方式建立線程比較好(好比:繼承thread類仍是調用Runnable接口),而後逐漸問到併發問題像在Java併發編程的過程當中遇到了什麼挑戰,Java內存模型,JDK1.5引入了哪些更高階的併發工具,併發編程經常使用的設計模式,經典多線程問題如生產者消費者,哲學家就餐,讀寫器或者簡單的有界緩衝區問題。僅僅知道線程的基本概念是遠遠不夠的, 你必須知道如何處理死鎖,競態條件,內存衝突和線程安全等併發問題。掌握了這些技巧,你就能夠輕鬆應對多線程和併發面試了。
許多Java程序員在面試前纔會去看面試題,這很正常。程序員

由於收集面試題和練習很花時間,因此我從許多面試者那裏收集了Java多線程和併發相關的50個熱門問題。面試

關注微信公衆號 "搜雲庫" 獲取最新文章

【福利】公衆號後臺回覆 「進羣」 拉你進微信【技術分享羣】

【福利】在裏面你能夠認識到不少搞技術大佬,免費提問,互相學習

圖片描述

下面是Java線程相關的熱門面試題,你能夠用它來好好準備面試。算法

【前25題】想進大廠?50個多線程面試題,你會多少?(一)

前25題想進大廠?50個多線程面試題,你會多少?(一)編程

  1. 什麼是線程?
  2. 什麼是線程安全和線程不安全?
  3. 什麼是自旋鎖?
  4. 什麼是Java內存模型?
  5. 什麼是CAS?
  6. 什麼是樂觀鎖和悲觀鎖?
  7. 什麼是AQS?
  8. 什麼是原子操做?在Java Concurrency API中有哪些原子類(atomic classes)?
  9. 什麼是Executors框架?
  10. 什麼是阻塞隊列?如何使用阻塞隊列來實現生產者-消費者模型?
  11. 什麼是Callable和Future?
  12. 什麼是FutureTask?
  13. 什麼是同步容器和併發容器的實現?
  14. 什麼是多線程?優缺點?
  15. 什麼是多線程的上下文切換?
  16. ThreadLocal的設計理念與做用?
  17. ThreadPool(線程池)用法與優點?
  18. Concurrent包裏的其餘東西:ArrayBlockingQueue、CountDownLatch等等。
  19. synchronized和ReentrantLock的區別?
  20. Semaphore有什麼做用?
  21. Java Concurrency API中的Lock接口(Lock interface)是什麼?對比同步它有什麼優點?
  22. Hashtable的size()方法中明明只有一條語句」return count」,爲何還要作同步?
  23. ConcurrentHashMap的併發度是什麼?
  24. ReentrantReadWriteLock讀寫鎖的使用?
  25. CyclicBarrier和CountDownLatch的用法及區別?
  26. LockSupport工具?
  27. Condition接口及其實現原理?
  28. Fork/Join框架的理解?
  29. wait()和sleep()的區別?
  30. 線程的五個狀態(五種狀態,建立、就緒、運行、阻塞和死亡)?
  31. start()方法和run()方法的區別?
  32. Runnable接口和Callable接口的區別?
  33. volatile關鍵字的做用?
  34. Java中如何獲取到線程dump文件?
  35. 線程和進程有什麼區別?
  36. 線程實現的方式有幾種(四種)?
  37. 高併發、任務執行時間短的業務怎樣使用線程池?併發不高、任務執行時間長的業務怎樣使用線程池?併發高、業務執行時間長的業務怎樣使用線程池?
  38. 若是你提交任務時,線程池隊列已滿,這時會發生什麼?
  39. 鎖的等級:方法鎖、對象鎖、類鎖?
  40. 若是同步塊內的線程拋出異常會發生什麼?
  41. 併發編程(concurrency)並行編程(parallellism)有什麼區別?
  42. 如何保證多線程下 i++ 結果正確?
  43. 一個線程若是出現了運行時異常會怎麼樣?
  44. 如何在兩個線程之間共享數據?
  45. 生產者消費者模型的做用是什麼?
  46. 怎麼喚醒一個阻塞的線程?
  47. Java中用到的線程調度算法是什麼
  48. 單例模式的線程安全性?
  49. 線程類的構造方法、靜態塊是被哪一個線程調用的?
  50. 同步方法和同步塊,哪一個是更好的選擇?
  51. 如何檢測死鎖?怎麼預防死鎖?

【前25題】想進大廠?50個多線程面試題,你會多少?(一)

前25題 想進大廠?50個多線程面試題,你會多少?(一)設計模式

CyclicBarrier和CountDownLatch的用法及區別?

CyclicBarrier和CountDownLatch 都位於java.util.concurrent 這個包下api

CountDownLatch CyclicBarrier
減計數方式 加計數方式
計算爲0時釋放全部等待的線程 計數達到指定值時釋放全部等待線程
計數爲0時,沒法重置 計數達到指定值時,計數置爲0從新開始
調用countDown()方法計數減一,調用await()方法只進行阻塞,對計數沒任何影響 調用await()方法計數加1,若加1後的值不等於構造方法的值,則線程阻塞
不可重複利用 可重複利用

1、CountDownLatch用法

CountDownLatch類只提供了一個構造器:數組

public CountDownLatch(int count) {  };  //參數count爲計數值

而後下面這3個方法是CountDownLatch類中最重要的方法:緩存

public void await() throws InterruptedException { };   //調用await()方法的線程會被掛起,它會等待直到count值爲0才繼續執行  
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };  //和await()相似,只不過等待必定的時間後count值還沒變爲0的話就會繼續執行  
public void countDown() { };  //將count值減1

CountDownLatch, 一個同步輔助類,在完成一組正在其餘線程中執行的操做以前,它容許一個或多個線程一直等待。

下面舉個例子說明:

package main.java.CountDownLatch;  
  
import java.util.concurrent.CountDownLatch;  
  
/** 
 * PROJECT_NAME:downLoad 
 * Author:lucaifang 
 * Date:2016/3/18 
 */  
public class countDownlatchTest {  
    public static void main(String[] args) throws InterruptedException {  
        CountDownLatch countDownLatch = new CountDownLatch(5);  
        for(int i=0;i<5;i++){  
            new Thread(new readNum(i,countDownLatch)).start();  
        }  
        countDownLatch.await();  
        System.out.println("線程執行結束。。。。");  
    }  
  
    static class readNum  implements Runnable{  
        private int id;  
        private CountDownLatch latch;  
        public readNum(int id,CountDownLatch latch){  
            this.id = id;  
            this.latch = latch;  
        }  
        @Override  
        public void run() {  
            synchronized (this){  
                System.out.println("id:"+id);  
                latch.countDown();  
                System.out.println("線程組任務"+id+"結束,其餘任務繼續");  
            }  
        }  
    }  
}

輸出結果:

id:1
線程組任務1結束,其餘任務繼續
id:0
線程組任務0結束,其餘任務繼續
id:2
線程組任務2結束,其餘任務繼續
id:3
線程組任務3結束,其餘任務繼續
id:4
線程組任務4結束,其餘任務繼續
線程執行結束。。。。

線程在countDown()以後,會繼續執行本身的任務

2、CyclicBarrier用法

CyclicBarrier會在全部線程任務結束以後,纔會進行後續任務,具體能夠看下面例子

CyclicBarrier提供2個構造器:

public CyclicBarrier(int parties, Runnable barrierAction) {  
}  
   
public CyclicBarrier(int parties) {  
}

參數parties指讓多少個線程或者任務等待至barrier狀態;參數barrierAction爲當這些線程都達到barrier狀態時會執行的內容。
CyclicBarrier中最重要的方法就是await方法

//掛起當前線程,直至全部線程都到達barrier狀態再同時執行後續任務;  
public int await() throws InterruptedException, BrokenBarrierException { };

//讓這些線程等待至必定的時間,若是還有線程沒有到達barrier狀態就直接讓到達barrier的線程執行後續任務  
public int await(long timeout, TimeUnit unit)throws InterruptedException,BrokenBarrierException,TimeoutException { };

舉例說明

package main.java.countOff;  
  
import java.util.concurrent.CyclicBarrier;  
  
/** 
 * PROJECT_NAME:downLoad 
 * Author:lucaifang 
 * Date:2016/3/18 
 */  
public class cyclicBarrierTest {  
    public static void main(String[] args) throws InterruptedException {  
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable() {  
            @Override  
            public void run() {  
                System.out.println("線程組執行結束");  
            }  
        });  
        for (int i = 0; i < 5; i++) {  
            new Thread(new readNum(i,cyclicBarrier)).start();  
        }  
        //CyclicBarrier 能夠重複利用,  
        // 這個是CountDownLatch作不到的  
//        for (int i = 11; i < 16; i++) {  
//            new Thread(new readNum(i,cyclicBarrier)).start();  
//        }  
    }  
    static class readNum  implements Runnable{  
        private int id;  
        private CyclicBarrier cyc;  
        public readNum(int id,CyclicBarrier cyc){  
            this.id = id;  
            this.cyc = cyc;  
        }  
        @Override  
        public void run() {  
            synchronized (this){  
                System.out.println("id:"+id);  
                try {  
                    cyc.await();  
                    System.out.println("線程組任務" + id + "結束,其餘任務繼續");  
                } catch (Exception e) {  
                    e.printStackTrace();  
                }  
            }  
        }  
    }  
}

輸出結果:

id:1
id:2
id:4
id:0
id:3
線程組執行結束
線程組任務3結束,其餘任務繼續
線程組任務1結束,其餘任務繼續
線程組任務4結束,其餘任務繼續
線程組任務0結束,其餘任務繼續
線程組任務2結束,其餘任務繼續

http://blog.csdn.net/tolcf/article/details/50925145

LockSupport工具?

一、LockSupport基本介紹與基本使用

LockSupport是JDK中比較底層的類,用來建立鎖和其餘同步工具類的基本線程阻塞。java鎖和同步器框架的核心 AQS: AbstractQueuedSynchronizer,就是經過調用 LockSupport .park()和 LockSupport .unpark()實現線程的阻塞和喚醒 的。

LockSupport 很相似於二元信號量(只有1個許可證可供使用),若是這個許可尚未被佔用,當前線程獲取許可並繼 續 執行;若是許可已經被佔用,當前線 程阻塞,等待獲取許可。

所有操做:

  • park()/park(Object)
  • 等待通行准許。
  • parkNanos(long)/parkNanos(Object, long)
  • 在指定運行時間(即相對時間)內,等待通行准許。
  • parkUntil(long)/parkUntil(Object, long)
  • 在指定到期時間(即絕對時間)內,等待通行准許。
  • unpark(Thread)
  • 發放通行准許或提早發放。(注:無論提早發放多少次,只用於一次性使用。)
  • getBlocker(Thread)
  • 進入等待通行准許時,所提供的對象。

主要用途:

當前線程須要喚醒另外一個線程,可是隻肯定它會進入阻塞,但不肯定它是否已經進入阻塞,所以無論是否已經進入阻塞,仍是準備進入阻塞,都將發放一個通行准許

正確用法:

把LockSupport視爲一個sleep()來用,只是sleep()是定時喚醒,LockSupport既能夠定時喚醒,也能夠由其它線程喚醒

public static void main(String[] args)
{
     LockSupport.park();
     System.out.println("block.");
}

運行該代碼,能夠發現主線程一直處於阻塞狀態。由於 許可默認是被佔用的 ,調用park()時獲取不到許可,因此進入阻塞狀態。

以下代碼:先釋放許可,再獲取許可,主線程可以正常終止。LockSupport許可的獲取和釋放,通常來講是對應的,若是屢次unpark,只有一次park也不會出現什麼問題,結果是許可處於可用狀態。

public static void main(String[] args)
{
     Thread thread = Thread.currentThread();
     LockSupport.unpark(thread);//釋放許可
     LockSupport.park();// 獲取許可
     System.out.println("b");
}

LockSupport是不可重入 的,若是一個線程連續2次調用 LockSupport .park(),那麼該線程必定會一直阻塞下去。

public static void main(String[] args) throws Exception
{
  Thread thread = Thread.currentThread();
  
  LockSupport.unpark(thread);
  
  System.out.println("a");
  LockSupport.park();
  System.out.println("b");
  LockSupport.park();
  System.out.println("c");
}

這段代碼打印出a和b,不會打印c,由於第二次調用park的時候,線程沒法獲取許可出現死鎖。

LockSupport基本介紹與基本使用

http://www.javashuo.com/article/p-kxfabitl-dp.html

LockSupport基本介紹與基本使用

http://www.tianshouzhi.com/api/tutorials/mutithread/303

Condition接口及其實現原理?

  • 在java.util.concurrent包中,有兩個很特殊的工具類,Condition和ReentrantLock,使用過的人都知道,ReentrantLock(重入鎖)是jdk的concurrent包提供的一種獨佔鎖的實現
  • 咱們知道在線程的同步時可使一個線程阻塞而等待一個信號,同時放棄鎖使其餘線程能夠能競爭到鎖
  • 在synchronized中咱們可使用Object的wait()和notify方法實現這種等待和喚醒
  • 可是在Lock中怎麼實現這種wait和notify呢
  • 答案是Condition,學習Condition主要是爲了方便之後學習blockqueue和concurrenthashmap的源碼,同時也進一步理解ReentrantLock。

ReentrantLock和Condition的使用方式一般是這樣的:

public static void main(String[] args) {
    final ReentrantLock reentrantLock = new ReentrantLock();
    final Condition condition = reentrantLock.newCondition();
 
    Thread thread = new Thread((Runnable) () -> {
            try {
                reentrantLock.lock();
                System.out.println("我要等一個新信號" + this);
                condition.wait();
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("拿到一個信號!!" + this);
            reentrantLock.unlock();
    }, "waitThread1");
 
    thread.start();
     
    Thread thread1 = new Thread((Runnable) () -> {
            reentrantLock.lock();
            System.out.println("我拿到鎖了");
            try {
                Thread.sleep(3000);
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
            condition.signalAll();
            System.out.println("我發了一個信號!!");
            reentrantLock.unlock();
    }, "signalThread");
     
    thread1.start();
}

運行後,結果以下:

我要等一個新信號lock.ReentrantLockTest$1@a62fc3
我拿到鎖了
我發了一個信號!!
拿到一個信號!!

能夠看到

Condition的執行方式,是當在線程1中調用await方法後,線程1將釋放鎖,而且將本身沉睡,等待喚醒,

線程2獲取到鎖後,開始作事,完畢後,調用Condition的signal方法,喚醒線程1,線程1恢復執行。

以上說明Condition是一個多線程間協調通訊的工具類,使得某個,或者某些線程一塊兒等待某個條件(Condition),只有當該條件具有( signal 或者 signalAll方法被帶調用)時 ,這些等待線程纔會被喚醒,從而從新爭奪鎖。

Condition本身也維護了一個隊列,該隊列的做用是維護一個等待signal信號的隊列,兩個隊列的做用是不一樣,事實上,每一個線程也僅僅會同時存在以上兩個隊列中的一個,流程是這樣的

  • 線程1調用reentrantLock.lock時,線程被加入到AQS的等待隊列中。
  • 線程1調用await方法被調用時,該線程從AQS中移除,對應操做是鎖的釋放。
  • 接着立刻被加入到Condition的等待隊列中,覺得着該線程須要signal信號。
  • 線程2,由於線程1釋放鎖的關係,被喚醒,並判斷能夠獲取鎖,因而線程2獲取鎖,並被加入到AQS的等待隊列中。
  • 線程2調用signal方法,這個時候Condition的等待隊列中只有線程1一個節點,因而它被取出來,並被加入到AQS的等待隊列中。 注意,這個時候,線程1 並無被喚醒。
  • signal方法執行完畢,線程2調用reentrantLock.unLock()方法,釋放鎖。這個時候由於AQS中只有線程1,因而,AQS釋放鎖後按從頭至尾的順序喚醒線程時,線程1被喚醒,因而線程1回覆執行。
  • 直到釋放所整個過程執行完畢。
  • 能夠看到,整個協做過程是靠結點在AQS的等待隊列和Condition的等待隊列中來回移動實現的,Condition做爲一個條件類,很好的本身維護了一個等待信號的隊列,並在適時的時候將結點加入到AQS的等待隊列中來實現的喚醒操做。

怎麼理解Condition

http://www.importnew.com/9281.html

深刻理解Condition

https://www.jianshu.com/p/6b5aa7b7684c

Fork/Join框架的理解?

Fork/Join是什麼

Oracle的官方給出的定義是:Fork/Join框架是一個實現了ExecutorService接口的多線程處理器。它能夠把一個大的任務劃分爲若干個小的任務併發執行,充分利用可用的資源,進而提升應用的執行效率。

咱們再經過Fork和Join這兩個單詞來理解下Fork/Join框架,Fork就是把一個大任務切分爲若干子任務並行的執行,Join就是合併這些子任務的執行結果,最後獲得這個大任務的結果

好比計算1+2+。。+10000,能夠分割成10個子任務,每一個子任務分別對1000個數進行求和,最終彙總這10個子任務的結果。

工做竊取算法

工做竊取算法是指線程從其餘任務隊列中竊取任務執行(可能你會很詫異,這個算法有什麼用。待會你就知道了)。考慮下面這種場景:有一個很大的計算任務,爲了減小線程的競爭,會將這些大任務切分爲小任務並分在不一樣的隊列等待執行,而後爲每一個任務隊列建立一個線程執行隊列的任務。那麼問題來了,有的線程可能很快就執行完了,而其餘線程還有任務沒執行完,執行完的線程與其空閒下來不如幫助其餘線程執行任務,這樣也能加快執行進程。因此,執行完的空閒線程從其餘隊列的尾部竊取任務執行,而被竊取任務的線程則從隊列的頭部取任務執行(這裏使用了雙端隊列,既不影響被竊取任務的執行過程又能加快執行進度)。

從以上的介紹中,可以發現工做竊取算法的優勢是充分利用線程提升並行執行的進度。固然缺點是在某些狀況下仍然存在競爭,好比雙端隊列只有任務須要執行的時候

使用Fork/Join框架分爲兩步:

分割任務:首先須要建立一個ForkJoin任務,執行該類的fork方法能夠對任務不斷切割,直到分割的子任務足夠小

合併任務執行結果:子任務執行的結果同一放在一個隊列中,經過啓動一個線程從隊列中取執行結果。

Fork/Join實現了ExecutorService,因此它的任務也須要放在線程池中執行。它的不一樣在於它使用了工做竊取算法,空閒的線程能夠從滿負荷的線程中竊取任務來幫忙執行

shitong

下面是計算1+2+3+4爲例演示如何使用使用Fork/Join框架:

package com.rhwayfun.concurrency.r0406;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;

/**
 * Created by rhwayfun on 16-4-6.
 */
public class CountTask extends RecursiveTask<Integer>{

    //閾值
    private static final int THRESHOLD = 2;
    //起始值
    private int start;
    //結束值
    private int end;

    public CountTask(int start, int end) {
        this.start = start;
        this.end = end;
    }


    @Override
    protected Integer compute() {
        boolean compute = (end - start) <= THRESHOLD;
        int res = 0;
        if (compute){
            for (int i = start; i <= end; i++){
                res += i;
            }
        }else {
            //若是長度大於閾值,則分割爲小任務
            int mid = (start + end) / 2;
            CountTask task1 = new CountTask(start,mid);
            CountTask task2 = new CountTask(mid + 1, end);
            //計算小任務的值
            task1.fork();
            task2.fork();
            //獲得兩個小任務的值
            int task1Res = task1.join();
            int task2Res = task2.join();
            res = task1Res + task2Res;
        }
        return res;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ForkJoinPool pool = new ForkJoinPool();
        CountTask task = new CountTask(1,5);
        ForkJoinTask<Integer> submit = pool.submit(task);
        System.out.println("Final result:" + submit.get());
    }
}

代碼執行結果爲:

15

代碼中使用了FokJoinTask,其與通常任務的區別在於它須要實現compute方法,在方法須要判斷任務是否在閾值區間內,若是不是則須要把任務切分到足夠小,直到可以進行計算

每一個被切分的子任務又會從新進入compute方法,再繼續判斷是否須要繼續切分,若是不須要則直接獲得子任務執行的結果,若是須要的話則繼續切分,如此循環,直到調用join方法獲得最終的結果
**
能夠發現Fork/Join框架的須要把提交給ForkJoinPool,ForkJoinPool由ForkJoinTask數組和ForkJoinWorkerThread數組組成,前者負責將存放程序提交給ForkJoinPool的任務,後者則負責執行這些任務。關鍵在於在於fork方法與join方法**

Java併發編程系列之二十:Fork/Join框架

http://blog.csdn.net/u011116672/article/details/51073683

wait()和sleep()的區別?

sleep()

方法是線程類(Thread)的靜態方法,讓調用線程進入睡眠狀態,讓出執行機會給其餘線程,等到休眠時間結束後,線程進入就緒狀態和其餘線程一塊兒競爭cpu的執行時間。

由於sleep() 是static靜態的方法,他不能改變對象的機鎖,當一個synchronized塊中調用了sleep() 方法,線程雖然進入休眠,可是對象的機鎖沒有被釋放,其餘線程依然沒法訪問這個對象。

wait()

wait()是Object類的方法,當一個線程執行到wait方法時,它就進入到一個和該對象相關的等待池,同時釋放對象的機鎖,使得其餘線程可以訪問,能夠經過notify,notifyAll方法來喚醒等待的線程

線程的五個狀態(五種狀態,建立、就緒、運行、阻塞和死亡)?

線程一般都有五種狀態,建立、就緒、運行、阻塞和死亡。

  • 第一是建立狀態。在生成線程對象,並無調用該對象的start方法,這是線程處於建立狀態。
  • 第二是就緒狀態。當調用了線程對象的start方法以後,該線程就進入了就緒狀態,可是此時線程調度程序尚未把該線程設置爲當前線程,此時處於就緒狀態。在線程運行以後,從等待或者睡眠中回來以後,也會處於就緒狀態。
  • 第三是運行狀態。線程調度程序將處於就緒狀態的線程設置爲當前線程,此時線程就進入了運行狀態,開始運行run函數當中的代碼。
  • 第四是阻塞狀態。線程正在運行的時候,被暫停,一般是爲了等待某個時間的發生(好比說某項資源就緒)以後再繼續運行。sleep,suspend,wait等方法均可以致使線程阻塞。
  • 第五是死亡狀態。若是一個線程的run方法執行結束或者調用stop方法後,該線程就會死亡。對於已經死亡的線程,沒法再使用start方法令其進入就緒

  

start()方法和run()方法的區別?

每一個線程都是經過某個特定Thread對象所對應的方法run()來完成其操做的,方法run()稱爲線程體。經過調用Thread類的start()方法來啓動一個線程。

start()方法啓動一個線程,真正實現了多線程運行。這時無需等待run方法體代碼執行完畢,能夠直接繼續執行下面的代碼;
這時此線程是處於就緒狀態, 並無運行。 而後經過此Thread類調用方法run()來完成其運行狀態, 這裏方法run()稱爲線程體,它包含了要執行的這個線程的內容, Run方法運行結束, 此線程終止。而後CPU再調度其它線程。

run()方法是在本線程裏的,只是線程裏的一個函數,而不是多線程的。
若是直接調用run(),其實就至關因而調用了一個普通函數而已,直接待用run()方法必須等待run()方法執行完畢才能執行下面的代碼,因此執行路徑仍是隻有一條,根本就沒有線程的特徵,因此在多線程執行時要使用start()方法而不是run()方法。

Runnable接口和Callable接口的區別?

有點深的問題了,也看出一個Java程序員學習知識的廣度。

  • Runnable接口中的run()方法的返回值是void,它作的事情只是純粹地去執行run()方法中的代碼而已;
  • Callable接口中的call()方法是有返回值的,是一個泛型,和Future、FutureTask配合能夠用來獲取異步執行的結果。

這實際上是頗有用的一個特性,由於多線程相比單線程更難、更復雜的一個重要緣由就是由於多線程充滿着未知性,某條線程是否執行了?某條線程執行了多久?某條線程執行的時候咱們指望的數據是否已經賦值完畢?沒法得知,咱們能作的只是等待這條多線程的任務執行完畢而已。而Callable+Future/FutureTask卻能夠獲取多線程運行的結果,能夠在等待時間太長沒獲取到須要的數據的狀況下取消該線程的任務,真的是很是有用。

volatile關鍵字的做用?

volatile關鍵字的做用主要有兩個:

(1)多線程主要圍繞可見性和原子性兩個特性而展開,使用volatile關鍵字修飾的變量,保證了其在多線程之間的可見性,即每次讀取到volatile變量,必定是最新的數據

(2)代碼底層執行不像咱們看到的高級語言—-Java程序這麼簡單,它的執行是Java代碼–>字節碼–>根據字節碼執行對應的C/C++代碼–>C/C++代碼被編譯成彙編語言–>和硬件電路交互,現實中,爲了獲取更好的性能JVM可能會對指令進行重排序,多線程下可能會出現一些意想不到的問題。使用volatile則會對禁止語義重排序,固然這也必定程度上下降了代碼執行效率

從實踐角度而言,volatile的一個重要做用就是和CAS結合,保證了原子性,詳細的能夠參見java.util.concurrent.atomic包下的類,好比AtomicInteger

Java中如何獲取到線程dump文件?

死循環、死鎖、阻塞、頁面打開慢等問題,打線程dump是最好的解決問題的途徑。所謂線程dump也就是線程堆棧,獲取到線程堆棧有兩步:

(1)獲取到線程的pid,能夠經過使用jps命令,在Linux環境下還可使用ps -ef | grep java

(2)打印線程堆棧,能夠經過使用jstack pid命令,在Linux環境下還可使用kill -3 pid

另外提一點,Thread類提供了一個getStackTrace()方法也能夠用於獲取線程堆棧。這是一個實例方法,所以此方法是和具體線程實例綁定的,每次獲取獲取到的是具體某個線程當前運行的堆棧,

虛擬機性能監控與故障處理工具 詳解

http://www.ymq.io/2017/08/01/jvm-4/

線程和進程有什麼區別?

  • 進程是系統進行資源分配的基本單位,有獨立的內存地址空間
  • 線程是CPU獨立運行和獨立調度的基本單位,沒有單獨地址空間,有獨立的棧,局部變量,寄存器, 程序計數器等。
  • 建立進程的開銷大,包括建立虛擬地址空間等須要大量系統資源
  • 建立線程開銷小,基本上只有一個內核對象和一個堆棧。
  • 一個進程沒法直接訪問另外一個進程的資源;同一進程內的多個線程共享進程的資源。
  • 進程切換開銷大,線程切換開銷小;進程間通訊開銷大,線程間通訊開銷小。
  • 線程屬於進程,不能獨立執行。每一個進程至少要有一個線程,成爲主線程

線程實現的方式有幾種(四種)?

  1. 繼承Thread類,重寫run方法
  2. 實現Runnable接口,重寫run方法,實現Runnable接口的實現類的實例對象做爲Thread構造函數的target
  3. 實現Callable接口經過FutureTask包裝器來建立Thread線程
  4. 經過線程池建立線程

前面兩種能夠歸結爲一類:無返回值,緣由很簡單,經過重寫run方法,run方式的返回值是void,因此沒有辦法返回結果

後面兩種能夠歸結成一類:有返回值,經過Callable接口,就要實現call方法,這個方法的返回值是Object,因此返回的結果能夠放在Object對象中

線程實現方式3:經過Callable和FutureTask建立線程

  1. 建立Callable接口的實現類 ,並實現Call方法
  2. 建立Callable實現類的實現,使用FutureTask類包裝Callable對象,該FutureTask對象封裝了Callable對象的Call方法的返回值
  3. 使用FutureTask對象做爲Thread對象的target建立並啓動線程
  4. 調用FutureTask對象的get()來獲取子線程執行結束的返回值
public class ThreadDemo03 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        Callable<Object> oneCallable = new Tickets<Object>();
        FutureTask<Object> oneTask = new FutureTask<Object>(oneCallable);

        Thread t = new Thread(oneTask);

        System.out.println(Thread.currentThread().getName());

        t.start();

    }

}

class Tickets<Object> implements Callable<Object>{

    //重寫call方法
    @Override
    public Object call() throws Exception {
        // TODO Auto-generated method stub
        System.out.println(Thread.currentThread().getName()+"-->我是經過實現Callable接口經過FutureTask包裝器來實現的線程");
        return null;
    }   
}

程序運行結果:

main
Thread-0–>我是經過實現Callable接口經過FutureTask包裝器來實現的線程

線程實現方式4:經過線程池建立線程

public class ThreadDemo05{

    private static int POOL_NUM = 10;     //線程池數量

    public static void main(String[] args) throws InterruptedException {
        // TODO Auto-generated method stub
        ExecutorService executorService = Executors.newFixedThreadPool(5);  
        for(int i = 0; i<POOL_NUM; i++)  
        {  
            RunnableThread thread = new RunnableThread();

            //Thread.sleep(1000);
            executorService.execute(thread);  
        }
        //關閉線程池
        executorService.shutdown(); 
    }   

}

class RunnableThread implements Runnable  
{     
    @Override
    public void run()  
    {  
        System.out.println("經過線程池方式建立的線程:" + Thread.currentThread().getName() + " ");  

    }  
}

程序運行結果:

經過線程池方式建立的線程:pool-1-thread-3 
經過線程池方式建立的線程:pool-1-thread-4 
經過線程池方式建立的線程:pool-1-thread-1 
經過線程池方式建立的線程:pool-1-thread-5 
經過線程池方式建立的線程:pool-1-thread-2 
經過線程池方式建立的線程:pool-1-thread-5 
經過線程池方式建立的線程:pool-1-thread-1 
經過線程池方式建立的線程:pool-1-thread-4 
經過線程池方式建立的線程:pool-1-thread-3 
經過線程池方式建立的線程:pool-1-thread-2

ExecutorService、Callable都是屬於Executor框架。返回結果的線程是在JDK1.5中引入的新特徵,還有Future接口也是屬於這個框架,有了這種特徵獲得返回值就很方便了。
經過分析能夠知道,他一樣也是實現了Callable接口,實現了Call方法,因此有返回值。這也就是正好符合了前面所說的兩種分類

執行Callable任務後,能夠獲取一個Future的對象在該對象上調用get就能夠獲取到Callable任務返回的Object了。get方法是阻塞的,即:線程無返回結果,get方法會一直等待

再介紹Executors類:

newCachedThreadPool建立一個可緩存線程池,若是線程池長度超過處理須要,可靈活回收空閒線程,若無可回收,則新建線程。

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

newScheduledThreadPool 建立一個定長線程池,支持定時及週期性任務執行。

newSingleThreadExecutor 建立一個單線程化的線程池,它只會用惟一的工做線程來執行任務,保證全部任務按照指定順序(FIFO, LIFO, 優先級)執行。

Java多線程實現的四種方式

http://blog.csdn.net/u011480603/article/details/75332435

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

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

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

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

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

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

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

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

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

鎖的等級:方法鎖、對象鎖、類鎖?

方法鎖(synchronized修飾方法時)

經過在方法聲明中加入 synchronized關鍵字來聲明 synchronized 方法。

synchronized 方法控制對類成員變量的訪問:
每一個類實例對應一把鎖,每一個 synchronized 方法都必須得到調用該方法的類實例的鎖方能執行,不然所屬線程阻塞,方法一旦執行,就獨佔該鎖,直到從該方法返回時纔將鎖釋放,此後被阻塞的線程方能得到該鎖,從新進入可執行狀態。這種機制確保了同一時刻對於每個類實例,其全部聲明爲 synchronized 的成員函數中至多隻有一個處於可執行狀態,從而有效避免了類成員變量的訪問衝突。

對象鎖(synchronized修飾方法或代碼塊)

當一個對象中有synchronized method或synchronized block的時候調用此對象的同步方法或進入其同步區域時,就必須先得到對象鎖。若是此對象的對象鎖已被其餘調用者佔用,則須要等待此鎖被釋放。(方法鎖也是對象鎖)       

java的全部對象都含有1個互斥鎖,這個鎖由JVM自動獲取和釋放。線程進入synchronized方法的時候獲取該對象的鎖,固然若是已經有線程獲取了這個對象的鎖,那麼當前線程會等待;synchronized方法正常返回或者拋異常而終止,JVM會自動釋放對象鎖。這裏也體現了用synchronized來加鎖的1個好處,方法拋異常的時候,鎖仍然能夠由JVM來自動釋放。 

類鎖(synchronized 修飾靜態的方法或代碼塊)

因爲一個class不論被實例化多少次,其中的靜態方法和靜態變量在內存中都只有一份。因此,一旦一個靜態的方法被申明爲synchronized。此類全部的實例化對象在調用此方法,共用同一把鎖,咱們稱之爲類鎖。  

對象鎖是用來控制實例方法之間的同步,類鎖是用來控制靜態方法(或靜態變量互斥體)之間的同步

若是同步塊內的線程拋出異常會發生什麼?

這個問題坑了不少Java程序員,若你能想到鎖是否釋放這條線索來回答還有點但願答對。不管你的同步塊是正常仍是異常退出的,裏面的線程都會釋放鎖,因此對比鎖接口我更喜歡同步塊,由於它不用我花費精力去釋放鎖,該功能能夠在finally block裏釋放鎖實現。

併發編程(concurrency)並行編程(parallellism)有什麼區別?

併發(concurrency)和並行(parallellism)是:

  1. 解釋一:並行是指兩個或者多個事件在同一時刻發生;而併發是指兩個或多個事件在同一時間間隔發生。
  2. 解釋二:並行是在不一樣實體上的多個事件,併發是在同一實體上的多個事件。
  3. 解釋三:在一臺處理器上「同時」處理多個任務,在多臺處理器上同時處理多個任務。如hadoop分佈式集羣

因此併發編程的目標是充分的利用處理器的每個核,以達到最高的處理性能。

如何保證多線程下 i++ 結果正確?

根據volatile特性來用1000個線程不斷的累加數字,每次累加1個,到最後值確不是1000.

volatile只能保證你數據的可見性(獲取到的是最新的數據,不能保證原子性,說白了,volatile跟原子性不要緊

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

public class Counter {

    public static AtomicInteger count = new AtomicInteger();//原子操做
    public static CountDownLatch latch= new CountDownLatch(1000);//線程協做處理
    public static volatile int countNum = 0;//volatile    只能保證可見性,不能保證原子性
    public static int synNum = 0;//同步處理計算

    public static void inc() {

        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
        }
        countNum++;
        int c = count.addAndGet(1);
        add();
        System.out.println(Thread.currentThread().getName() + "------>" + c);
    }

    public static synchronized void add(){
        synNum++;
    }

    public static void main(String[] args) {

        //同時啓動1000個線程,去進行i++計算,看看實際結果

        for (int i = 0; i < 1000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Counter.inc();
                    latch.countDown();
                }
            },"thread" + i).start();
        }
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName());

        System.out.println("運行結果:Counter.count=" + count.get() + ",,," + countNum + ",,," + synNum);
    }

count.get()是AtomicInteger的值;

count是用volatile修飾的變量的值;

synNum是用synchronized修飾的值;

用synchronized和AtomicInteger能保證是你想要的數據,volatile並不能保證

第一次運行結果

main
運行結果:Counter.count=1000,,,991,,,1000

第二次運行結果:

main
運行結果:Counter.count=1000,,,998,,,1000

第三次運行結果:

main
運行結果:Counter.count=1000,,,993,,,1000

可見,就算用了volatile,也不能保證數據是你想要的數據,volatile只能保證你數據的可見性(獲取到的是最新的數據,不能保證原子性,說白了,volatile跟原子性不要緊)

要保證原子性,對數據的累加,能夠用AtomicInteger類;

也能夠用synchronized來保證數據的一致性

一個線程若是出現了運行時異常會怎麼樣?

若是這個異常沒有被捕獲的話,這個線程就中止執行了。另外重要的一點是:若是這個線程持有某個某個對象的監視器,那麼這個對象監視器會被當即釋放

如何在兩個線程之間共享數據?

經過在線程之間共享對象就能夠了,而後經過wait/notify/notifyAll、await/signal/signalAll進行喚起和等待,比方說阻塞隊列BlockingQueue就是爲線程之間共享數據而設計的

生產者消費者模型的做用是什麼?

這個問題很理論,可是很重要:

(1)經過平衡生產者的生產能力和消費者的消費能力來提高整個系統的運行效率,這是生產者消費者模型最重要的做用

(2)解耦,這是生產者消費者模型附帶的做用,解耦意味着生產者和消費者之間的聯繫少,聯繫越少越能夠獨自發展而不須要收到相互的制約

怎麼喚醒一個阻塞的線程?

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

Java中用到的線程調度算法是什麼?

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

單例模式的線程安全性?

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

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

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

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

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

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

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

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

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

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

同步塊是更好的選擇,由於它不會鎖住整個對象(固然也可讓它鎖住整個對象)。同步方法會鎖住整個對象,哪怕這個類中有多個不相關聯的同步塊,這一般會致使他們中止執行並須要等待得到這個對象上的鎖。

public class SynObj{

public synchronized void showA(){
    System.out.println("showA..");
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

public void showB(){
    synchronized (this) {
        System.out.println("showB..");
    }
}

}

如何檢測死鎖?怎麼預防死鎖?

所謂死鎖:是指兩個或兩個以上的進程在執行過程當中,因爭奪資源而形成的一種互相等待的現象,若無外力做用,它們都將沒法推動下去。此時稱系統處於死鎖

通俗地講就是兩個或多個進程被無限期地阻塞、相互等待的一種狀態

死鎖產生的緣由?

1.因競爭資源發生死鎖 現象:系統中供多個進程共享的資源的數目不足以知足所有進程的須要時,就會引發對諸資源的競爭而發生死鎖現象

2.進程推動順序不當發生死鎖

死鎖的四個必要條件:

  1. 互斥條件:進程對所分配到的資源不容許其餘進程進行訪問,若其餘進程訪問該資源,只能等待,直至佔有該資源的進程使用完成後釋放該資源
  2. 請求和保持條件:進程得到必定的資源以後,又對其餘資源發出請求,可是該資源可能被其餘進程佔有,此事請求阻塞,但又對本身得到的資源保持不放
  3. 不可剝奪條件:是指進程已得到的資源,在未完成使用以前,不可被剝奪,只能在使用完後本身釋放
  4. 環路等待條件:是指進程發生死鎖後,若干進程之間造成一種頭尾相接的循環等待資源關係

這四個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之
一不知足,就不會發生死鎖。

檢測死鎖

有兩個容器,一個用於保存線程正在請求的鎖,一個用於保存線程已經持有的鎖。每次加鎖以前都會作以下檢測:

  1. 檢測當前正在請求的鎖是否已經被其它線程持有,若是有,則把那些線程找出來
  2. 遍歷第一步中返回的線程,檢查本身持有的鎖是否正被其中任何一個線程請求,若是第二步返回真,表示出現了死鎖

死鎖的解除與預防:

理解了死鎖的緣由,尤爲是產生死鎖的四個必要條件,就能夠最大可能地避免、預防和
解除死鎖。

因此,在系統設計、進程調度等方面注意如何不讓這四個必要條件成立,如何確
定資源的合理分配算法,避免進程永久佔據系統資源。

此外,也要防止進程在處於等待狀態的狀況下佔用資源。所以,對資源的分配要給予合理的規劃。

http://blog.csdn.net/yyf_it/a...
http://blog.csdn.net/yyf_it/article/details/52412071

推薦閱讀

Contact

  • 做者:鵬磊
  • 出處:http://www.ymq.io
  • 版權歸做者全部,轉載請註明出處
  • Wechat:關注公衆號,搜雲庫,專一於開發技術的研究與知識分享

關注微信公衆號 "搜雲庫" 獲取最新文章

【福利】公衆號後臺回覆 「進羣」 拉你進微信【技術分享羣】

【福利】在裏面你能夠認識到不少搞技術大佬,免費提問,互相學習

圖片描述

相關文章
相關標籤/搜索