java多線程-面試題

java多線程

  1. 多線程的幾種實現方式,什麼是線程安全。
  • 繼承Thread類,重寫run方法
  • 實現Runnable接口,重寫run方法,實現Runnable接口的實現類的實例對象作爲Thread構造函數的target
  • 通過Callable和FutrueTask創建線程
  • 線程安全:當多個線程訪問某一個類、對象、方法時,對象對應的公共數據區始終都能表現正確,那麼就是線程安全的。
  1. volatile的原理,作用,能代替鎖麼。
  • 作用:保持內存可見性防止指定重排序
  • 原理:
    • 每次變量讀取前必須先從主內存刷新最新值,每次寫入後必須同步回主內存中。
    • 編譯器在生成字節碼時,會在指令排序中插入內存屏障來禁止特定類型的處理器重排序。
  • volatile不能代替鎖,其不具有 原子性操作。例如:變量自增操作。
  1. 線程的生命週期狀態圖。

    線程生命週期圖

  2. sleep和wait的區別。

  • sleep是線程方法,wait是Object 方法
  • sleep不釋放lock, wait會釋放
  • sleep不依賴同步方法,wait需要
  • sleep不需要被喚醒,wait需要
  1. sleep和sleep(0)的區別。
  • 在線程沒退出之前,線程有三個狀態,就緒態,運行態,等待態。當線程調用sleep(n)的時候,線程是由運行態轉入等待態,線程被放入等待隊列中,等待定時器n秒後的中斷事件,當到達n秒計時後,線程才重新由等待態轉入就緒態,被放入就緒隊列中,等待隊列中的線程是不參與cpu競爭的,只有就緒隊列中的線程纔會參與cpu競爭
  • 而sleep(0)之所以馬上回去參與cpu競爭,是因爲調用sleep(0)後,因爲0的原因,線程直接回到就緒隊列,而非進入等待隊列,只要進入就緒隊列,那麼它就參與cpu競爭
  1. Lock與Synchronized的區別 。
  • synchronized是java內置關鍵字,在jvm層面,Lock是個java類;
  • synchronized無法判斷是否獲取鎖的狀態,Lock可以判斷
  • sysnchronized會自動釋放鎖,Lock需要在finally中手工釋放鎖
  • sysnchronized 的鎖可重入、不可中斷、非公平,Lock鎖可重入、可中斷、可公平(兩者皆可)
  1. synchronized的原理是什麼,一般用在什麼地方(比如加在靜態方法和非靜態方法的區別,靜

    態方法和非靜態方法同時執行的時候會有影響嗎),解釋以下名詞:重排序,自旋鎖,偏向鎖,輕

    量級鎖,可重入鎖,公平鎖,非公平鎖,樂觀鎖,悲觀鎖。

  • 在java中任何一個對象都有一個監視器(Monitor)與之對應,當一個Monitor被持有後,該對象處於鎖定狀態。synchronized在JVM裏的實現都是基於進入和退出monitor對象來實現方法的同步和代碼塊同步

    • MonitorEnter指令:插入在同步代碼塊開始位置,當代碼執行到該指令時,將會嘗試獲取該對象monitor的所有權,即嘗試獲得該對象的鎖。
    • MonitorExit指令:插入在方法結束處和異常處,jvm保證每個MonitorEnter必須有對應的MonitorExit.
  • synchronized 加在靜態方法上是鎖住整個類的,針對的是類。synchronized加在非靜態方法上是鎖住類的對象上,是針對對象的,對象之間沒有影響。靜

    態方法和非靜態方法同時執行的時候會產生互斥,是有影響的。

  • 名詞解釋:

    • 重排序:不改變單線程程序語義前提下,重新安排執行順序
    • 自旋鎖:當一個線程在獲取鎖時,如果鎖已經被其他線程獲取,那麼該線程將循環等待,然後不斷的判斷鎖是否能夠被成功獲取,知道獲取到鎖才推出循環。
    • 輕量級鎖:線程在執行同步塊之前,JVM會先在當前線程的棧幀中穿件用於存儲鎖記錄的空間,並將鎖對象的Mark Word複製到鎖記錄中。然後線程嘗試用CAS將對象頭中的Mark Work替換爲指向鎖記錄的指針。如果成功,當先線程獲得鎖,如果失敗自旋獲取鎖。再失敗鎖升級。
    • 偏向鎖:在輕量級鎖的基礎上繼續優化無資源競爭的情況。偏向鎖的目標是,減少無競爭且只有一個線程使用鎖的情況下,使用輕量級鎖產生的性能消耗。偏向鎖只有在初始化時需要一次CAS,將Mark Word中記錄ower,如果記錄成功,則偏向鎖獲取成功,記錄狀態爲偏向鎖,以後當前線程等於owner就可以直接獲取鎖。否做,索命有其他線程競爭,升級爲輕量級鎖。
    • 可重入鎖:可重入就是說某個線程已經獲得某個鎖,可以再次獲取鎖而不會出現死鎖
    • 公平鎖:加鎖前先查看是否有排隊等待的線程,有有限處理排在前邊的線程
    • 非公平鎖:線程加鎖時直接嘗試獲取鎖,獲取不到自動到隊尾等待
    • 樂觀鎖:假設不會發生併發衝突,直接不加鎖去操作,如果衝突直接返回(CAS)
    • 悲觀鎖:假設一定會發生併發衝突,通過阻塞其他線程來保證數據完整性
  1. 用過哪些原子類,他們的原理是什麼。
  • java.util.concurrent 包下提供了一些原子類:AtomicInteger、AtomicLong
  • 原子類中通過引入CAS機制,結束屬性偏移量計算預期值。並配合volatile,保證屬性原子性。
  1. JUC下研究過哪些併發工具,講講原理。
  • CountDownLatch :通過基數阻塞線程等待,內部通過lock實現。
  • CyclicBarrier: 循環柵欄,一組線程到達臨界點後恢復執行
  • Exchange: 線程交換器。用於兩個線程的數據交換,當Exchange只有一個線程時,該線程會阻塞直到有別的線程調用exchange緩衝區,當前線程與新線交換數據後同時恢復運行。
  • Semaphore:信號量。類似生產消費模式。
  1. 用過線程池嗎,如果用過,請說明原理,並說說newCache和newFixed有什麼區別,構造函

    數的各個參數的含義是什麼,比如coreSize,maxsize等。

  • ThreadPoolExecutor 類是線程池最核心的類,其構造參數:
    • corePoolSize: 核心池大小。在創建線程池後,線程池中的線程數爲0,當有任務來之後,就會創建一個線程去執行任務,當線程池中的線程數目達到corePoreSize後,就會把到達的任務放到緩存隊列當中。
    • maximumPoolSize:線程池最大線程數
    • keepAliveTime: 表示線程沒有任務執行時最多保持多久時間會終止。默認情況下,只有當線程池的線程數大於corePoolSize時纔會起作用。
    • unit:參數keepAliveTime的時間單位
    • workQueue: 一個阻塞隊列,用來存儲等待執行的任務
    • threadFactory:線程工廠,用來創建線程
    • handler: 表示當拒絕處理時用的策略
  • Executors 提供四種線池的默認實現:
    • newCachedThreadPool:緩存線程池,如果線程池長度超過處理需要,可回收空閒線程,若無可回收線程,則創建線程
    • newFixedThreadPool: 定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待
    • newScheduledThreadPool:計劃線程池,支持定時及週期性任務執行。
    • newSingleThreadExecutor:單線程線程池,用唯一線程來執行任務。
  1. 線程池的關閉方式有幾種,各自的區別是什麼。
  • shutdown
    • 調用後不允許線程池繼續添加線程,線程池的狀態爲shutdown狀態,池中已有任務會繼續執行,待所有任務執行完畢後線程池關閉。
  • shutdownNow
    • 該方法返回尚未執行的task的列表,線程池狀態變爲stop。阻止所有正在等待啓動的任務,並且停滯正在執行的任務
  1. 假如有一個第三方接口,有很多個線程去調用獲取數據,現在規定每秒鐘最多有10個線程同

    時調用它,如何做到。

  • 藉助Executors的計劃線程池ScheduledThreadPoolExecutor

    public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) { 	  
          super(corePoolSize, Integer.MAX_VALUE, 0,TimeUnit.NANOSECONDS, new DelayedWorkQueue(),threadFactory);
     }
    
  • 這個問題有時間要搞一下---------mark

  1. spring的controller是單例還是多例,怎麼保證併發的安全
  • springmvc 默認是單例的,是線程非安全的。
  • 進來避免在controller中定義實例變量,如果非要定義實例變量並且有多線程修改情況下可以嘗試一下方式處理:
    • 單例改多例 scope=“prototype”
    • 在controller中使用ThreadLocal變量。ThreadLocal會爲每一個線程提供獨立的變量副本,從而隔離多個線程對數據的訪問衝突。因爲每個線程都有自己的變量副本,從而也就沒有必要對該變量進行同步。
  1. 用三個線程按順序循環打印abc三個字母,比如abcabcabc。
  • synchronized 搭配wait/notify實現

    public class ThreadPrint implements Runnable{
        private String name;
        private Object prve;
        private Object value;
    
        public ThreadPrint(String name, Object prve, Object value) {
            this.name = name;
            this.prve = prve;
            this.value = value;
        }
    
        public void run() {
          int count = 3;
          while (count>0){
              synchronized (prve){
                  synchronized (value){
                      System.out.print(name);
                      count --;
                      value.notifyAll();
                  }
                  try {
                      if (count == 0){
                          prve.notifyAll();
                      }else {
                          prve.wait();
                      }
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
          }
        }
    
        public static void main(String[] args) throws Exception{
            Object a = new Object();
            Object b = new Object();
            Object c = new Object();
    
            ThreadPrint p1 = new ThreadPrint("a",c,a);
            ThreadPrint p2 = new ThreadPrint("b",a,b);
            ThreadPrint p3 = new ThreadPrint("c",b,c);
            new Thread(p1).start();
            Thread.sleep(10);
            new Thread(p2).start();
            Thread.sleep(10);
            new Thread(p3).start();
        }
    
  • lock鎖方式:

    public class ThreadPrintType2 {
        static Lock lock = new ReentrantLock();
        static int state = 0;
        static class ThreadA extends Thread{
            @Override
            public void run() {
                for (int i= 0;i<3;){
                    try {
                        lock.lock();
                        while (state%3 == 0){
                            System.out.print("a");
                            state++;
                            i++;
                        }
                    }finally {
                        lock.unlock();
                    }
                }
            }
        }
        static class ThreadB extends Thread{
            @Override
            public void run() {
                for (int i= 0;i<3;){
                    try {
                        lock.lock();
                        while (state%3 == 1){
                            System.out.print("b");
                            state++;
                            i++;
                        }
                    }finally {
                        lock.unlock();
                    }
                }
            }
        }
        static class ThreadC extends Thread{
            @Override
            public void run() {
                for (int i= 0;i<3;){
                    try {
                        lock.lock();
                        while (state%3 == 2){
                            System.out.print("c");
                            state++;
                            i++;
                        }
                    }finally {
                        lock.unlock();
                    }
                }
            }
        }
    
        public static void main(String[] args) {
            new ThreadA().start();
            new ThreadB().start();
            new ThreadC().start();
        }
    
  1. ThreadLocal用過麼,用途是什麼,原理是什麼,用的時候要注意什麼。
  • 實例變量如果涉及到多線程操作導致線程安全問題,可以藉助ThreadLocal,其提供線程變量副本,隔離線程之間的影響。其常用來解決數據庫連接、session管理的問題。
  • Thread爲每個線程維護了一個map:ThreadLocalMap.該map的key是LocalThread,value則是要存儲的對象。ThreadLocal本身並不存儲值,它只是作爲一個key來讓線程從ThreadLocalMap獲取value
  • ThreadLocalMap使用的是ThreadLocal的弱引用作爲key,如果一個ThreadLocal沒有外部引用,待系統gc時,該弱引用被回收,map中key值爲null,而與之對應的value會一致維持一個強引用鏈無法回收,造成內存泄漏。使用時應該注意調用remove函數,手動清除。
  1. 如果讓你實現一個併發安全的鏈表,你會怎麼做。
  • 在鏈表實現的基礎上加鎖,所有設計到鏈表結構變化的地點加鎖,例如,新增,刪除等操作。例如:藉助ReentrantLock()可重入鎖對新增結點加鎖,避免衝突。

    public class ConcurrentSingleLinedList{
        final static Lock lock = new ReentrantLock();
        Node head = null;
        static class Node {
            Node next = null;
            int data;
            public Node(int data){
                this.data = data;
            }
        }
        public void add(int data) {
            try {
                lock.lock();
                Node newNode = new Node(data);
                if(head == null){
                    head = newNode;
                    return;
                }
                Node temp = head;
                while(temp.next != null){
                    temp = temp.next;
                }
                temp.next = newNode;
            }finally {
                lock.unlock();
            }
        }
    }
    
  1. 有哪些無鎖數據結構,他們實現的原理是什麼。
  • java.util.concurrent.atomic 提供了AtomicInteger、AtomicLong、AtomicIntegerArray 等一系列的類,提供了一系列的原子操作。其實現藉助於CAS:對比變量的值和expect是否相等,如果相等則將變量的值更新爲update。
  1. 講講java同步機制的wait和notify。
  • java中 wait()和notify()方法都是Object方法。從功能上來說wait就是說線程在獲取對象鎖後,主動釋放對象鎖,同時本線程休眠。知道有其他線程調用對象notify()喚醒該線程,才能繼續獲取對象所,並繼續執行。相應的notify()就是對象鎖的喚醒操作。wait()和notify()需要在同步鎖中。
  1. CAS機制是什麼,如何解決ABA問題。
  • CAS:當需要更新一個變量的值得時候,只有當變量的預期值和內存地址中實際值相同的時候,纔會把內存地址對應的值替換。
  • ABA:CAS比較的是兩個時間節點的變量值變化,而在這兩個時間節點內的變化並不關心,由此引發了目標值時A,但是過程有可能是 A-B-A。結果一樣,過程不一定一致。解決該問題可以給操作添加版本號,jdk的atomic包裏提供了一個類AtomicStampedReference來接榫ABA問題。
  1. 多線程如果線程掛住了怎麼辦。
  • 如果是wait()方法使線程過期,可以使用notify()和notifyAll()喚醒
  • 如果是suspend掛起,可以通過resume方法喚醒。
  1. countdowlatch和cyclicbarrier的內部原理和用法,以及相互之間的差別(比如

    countdownlatch的await方法和是怎麼實現的)。

  • CountDownLatch: 一個線程等待其他多個線程完成一件事,纔可以繼續執行。
  • CyclicBarrier:線程之間相互等待,當所有線程都await之後,這些線程才繼續執行。
  • CountDownLatch減計數,CyclicBarrier加計數。CountDownLatch是一次性的,CyclicBarrier可以重用。
  • await() 方法,初始化一個隊列,將需要等待的線程加入隊列中,並用waitStatus標記後繼節點的線程狀態。
  1. 對AbstractQueuedSynchronizer瞭解多少,講講加鎖和解鎖的流程,獨佔鎖和公平所

    加鎖有什麼不同。

  • 獨佔鎖和共享鎖:獨佔鎖模式下,每次只能有一個線程能持有鎖,如:ReetrantLock。共享鎖:允許多個線程同時獲取鎖,併發訪問共享資源,如:ReadWriteLock.
  • 公平鎖和非公平鎖:在公平鎖上,線程將按他們發出請求的順序來獲得鎖;而非公平鎖則允許在線程發出請求後立即嘗試獲取鎖,如果可用則直接獲取鎖,嘗試失敗才進行排隊等待。
  • AQS是一個雙向鏈表。其每個節點中都包含了一個線程和一個類型變量,表示當前節點是獨佔節點還是共享節點,頭節點中的線程爲正在佔有鎖的線程,而後的所有節點的線程表示爲正在等待獲取鎖的線程。
  1. 使用synchronized修飾靜態方法和非靜態方法有什麼區別。
  • 對於類鎖synchronized static,是通過該類直接調用加類鎖的方法,而對象鎖是創建對象調用加對象鎖的方法,兩者訪問是不衝突的
  1. 簡述ConcurrentLinkedQueue和LinkedBlockingQueue的用處和不同之處
  • LinkedBlockingQueue 是阻塞隊列。實現是線程安全的,實現了先進先出等特性,是作爲成纏着消費者的首選
  • ConcurrentLinkedQueue 是非阻塞隊列。當許多線程共享訪問一個公共集合時時一個好的選擇,其採用CAS操作保證元素一致性。
  1. 導致線程死鎖的原因?怎麼解除線程死鎖。
  • 死鎖條件:
    • 互斥使用:資源只被一個線程使用
    • 不可搶佔: 不能搶奪資源
    • 請求和保持:保持已有資源佔用並請求佔有新資源
    • 循環等待: 線程間循環等待資源
  • 打破上述條件便可讓死鎖消失。
  • 避免死鎖:注意加鎖順序,線程間對資源的訪問時同一順序。加鎖設定時限,不可無限佔有。
  1. 非常多個線程(可能是不同機器),相互之間需要等待協調,才能完成某種工作,問怎麼設計這種協調方案。
  • 個人認爲可以參考countdownlatch中await實現,藉助隊列協調線程之間狀態。
  1. 用過讀寫鎖嗎,原理是什麼,一般在什麼場景下用。
  • 對於多個線程共享同一個資源的時候,多個線程同時對共享資源做讀操作是不會發生線程安全性問題的,但是一旦有一個線程對共享數據做寫操作其他的線程再來讀寫共享資源的話,就會發生數據安全性問題,所以出現了讀寫鎖ReentrantReadWriteLock。
  • 原理:如果有線程想申請讀鎖的話,首先會判斷寫鎖是否被持有,如果寫鎖被持有且當前線程並不是持有寫鎖的線程,那麼就會返回-1,獲取鎖失敗,進入到等待隊列等待。如果寫鎖未被線程所持有或者當前線程和持有寫鎖的線程是同一線程的話就會開始獲取讀鎖。藉助CAS來實現。
  • 場景:大量讀操作下,可以使用讀寫鎖來併發處理。
  1. 開啓多個線程,如果保證順序執行,有哪幾種實現方式,或者如何保證多個線程都執行完

    再拿到結果。

  • 保證順序執行可以參考14題的兩種實現
  • 保證多個線程都執行完再拿結果可以使用countdownlatch.
  1. 延遲隊列的實現方式,delayQueue和時間輪算法的異同。
  • DelayQueue:位於java.util.concurrent包下,DelayQueue本質是封裝了一個PriorityQueue,使之線程安全,加上Delay功能,也就是說,消費者線程只能在隊列中的消息“過期”之後才能返回數據獲取到消息,不然只能獲取到null。其數據結構採用最小堆,插入和獲取時,時間複雜度都爲O(logN)
  • 時間輪:是一種數據結構,其主體是一個循環列表(circular buffer),每個列表中包含一個稱之爲槽(slot)的結構。這個數據結構最重要的是兩個指針,一個是觸發任務的函數指針,另外一個是觸發的總第幾圈數。時間輪可以用簡單的數組或者是環形鏈表來實現。相比DelayQueue的數據結構,時間輪在算法複雜度上有一定優勢。DelayQueue由於涉及到排序,需要調堆,插入和移除的複雜度是O(lgn),而時間輪在插入和移除的複雜度都是O(1)。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章