敲開阿里大門的執行緒、多執行緒和執行緒池面試專題

yilian發表於2019-11-18

本篇文章主要介紹  Android 開發中的部分知識點,透過閱讀本篇文章,您將收穫以下內容:

1、開啟執行緒的三種方式?

1)繼承 Thread類,重寫 run()方法,在 run()方法體中編寫要完成的任務  new Thread().start();

2)實現 Runnable介面,實現 run()方法  new Thread(new MyRunnable()).start();

3)實現 Callable介面 MyCallable類,實現 call()方法,使用 FutureTask類來包裝 Callable物件,使用 FutureTask物件作為 Thread物件的 target建立並啟動執行緒;呼叫 FutureTask物件的 get()方法來獲得子執行緒執行結束後的返回值。

FutureTask<Integer> ft = new FutureTask<Integer>(new MyCallable());new Thread(ft).start();

2、run()和start()方法區別

run()方法只是執行緒的主體方法,和普通方法一樣,不會建立新的執行緒。
只有呼叫 start()方法,才會啟動一個新的執行緒,新執行緒才會呼叫 run()方法,執行緒才會開始執行。

3、如何控制某個方法允許併發訪問執行緒的個數?

建立 Semaphore變數, Semaphore semaphore = new Semaphore(5, true);當方法進入時,請求一個訊號,如果訊號被用完則等待,方法執行完,釋放一個訊號,釋放的訊號新的執行緒就可以使用。

4、在Java中wait和seelp方法的不同

wait()方法屬於 Object類,呼叫該方法時,執行緒會放棄物件鎖,只有該物件呼叫 notify()方法後本執行緒才進入物件鎖定池準備獲取物件鎖進入執行狀態。

sleep()方法屬於 Thread類, sleep()導致程式暫停執行指定的時間,讓出 CPU,但它的監控狀態依然儲存著,當指定時間到了又會回到執行狀態, sleep()方法中執行緒不會釋放物件鎖。

5、談談wait/notify關鍵字的理解

notify:
喚醒在此物件監視器上等待的單個執行緒

notifyAll():
通知所有等待該競爭資源的執行緒

wait:
釋放 obj的鎖,導致當前的執行緒等待,直接其他執行緒呼叫此物件的 notify()notifyAll()方法

當要呼叫 wait()notify()/notifyAll()方法時,一定要對競爭資源進行加鎖,一般放到 synchronized(obj)程式碼中。當呼叫 obj.notify/notifyAll後,呼叫執行緒依舊持有 obj鎖,因此等待執行緒雖被喚醒,但仍無法獲得 obj鎖,直到呼叫執行緒退出 synchronized塊,釋放 obj鎖後,其他等待執行緒才有機會獲得鎖繼續執行。

6、什麼導致執行緒阻塞?

(1)一般執行緒阻塞

1)執行緒執行了 Thread.sleep(int millsecond)方法,放棄 CPU,睡眠一段時間,一段時間過後恢復執行;

2)執行緒執行一段同步程式碼,但無法獲得相關的同步鎖,只能進入阻塞狀態,等到獲取到同步鎖,才能恢復執行;

3)執行緒執行了一個物件的 wait()方法,直接進入阻塞態,等待其他執行緒執行 notify()/notifyAll()操作;

4)執行緒執行某些 IO操作,因為等待相關資源而進入了阻塞態,如 System.in,但沒有收到鍵盤的輸入,則進入阻塞態。

5)執行緒禮讓, Thread.yield()方法,暫停當前正在執行的執行緒物件,把執行機會讓給相同或更高優先順序的執行緒,但並不會使執行緒進入阻塞態,執行緒仍處於可執行態,隨時可能再次分得 CPU時間。執行緒自閉, join()方法,在當前執行緒呼叫另一個執行緒的 join()方法,則當前執行緒進入阻塞態,直到另一個執行緒執行結束,當前執行緒再由阻塞轉為就緒態。

6)執行緒執行 suspend()使執行緒進入阻塞態,必須 resume()方法被呼叫,才能使執行緒重新進入可執行狀態。

7、執行緒如何關閉?

  1. 使用標誌位

2)使用 stop()方法,但該方法就像關掉電腦電源一樣,可能會發生預料不到的問題

3)使用中斷 interrupt()

public class Thread {
    // 中斷當前執行緒
    public void interrupt();    // 判斷當前執行緒是否被中斷
    public boolen isInterrupt();    // 清除當前執行緒的中斷狀態,並返回之前的值
    public static boolen interrupted();   
}

但呼叫 interrupt()方法只是傳遞中斷請求訊息,並不代表要立馬停止目標執行緒。

8、講一下java中的同步的方法

之所以需要同步,因為在多執行緒併發控制,當多個執行緒同時操作一個可共享的資源時,如果沒有采取同步機制,將會導致資料不準確,因此需要加入同步鎖,確保在該執行緒沒有完成操作前被其他執行緒呼叫,從而保證該變數的唯一一性和準確性。

1)synchronized修飾同步程式碼塊或方法

由於 java的每個物件都有一個內建鎖,用此關鍵字修飾方法時,內建鎖會保護整個方法。在呼叫該方法前,需獲得內建鎖,否則就處於陰塞狀態。

2)volatile修飾變數

保證變數線上程間的可見性,每次執行緒要訪問 volatile修飾的變數時都從記憶體中讀取,而不快取中,這樣每個執行緒訪問到的變數都是一樣的。且使用記憶體屏障。

3)ReentrantLock重入鎖,它常用的方法有ReentrantLock():

建立一個 ReentrantLock例項

lock()獲得鎖 unlock()釋放鎖

4)使用區域性變數ThreadLocal實現執行緒同步

每個執行緒都會儲存一份該變數的副本,副本之間相互獨立,這樣每個執行緒都可以隨意修改自己的副本,而不影響其他執行緒。常用方法 ThreadLocal()建立一個執行緒本地變數; get()返回此執行緒區域性的當前執行緒副本變數; initialValue()返回此執行緒區域性變數的當前執行緒的初始值; set(T value)將此執行緒變數的當前執行緒副本中的值設定為 value

5) 使用原子變數

AtomicInteger,常用方法 AtomicInteger(int value)建立個有給定初始值的 AtomicInteger整數; addAndGet(int data)以原子方式將給定值與當前值相加

6)使用阻塞佇列實現執行緒同步

例如 LinkedBlockingQueue<E>

9、如何保證執行緒安全?

執行緒安全性體現在三方法:

1)原子性:

提供互斥訪問,同一時刻只能有一個線和至資料進行操作。

JDK中提供了很多 atomic類,如 AtomicInteger\AtomicBoolean\AtomicLong,它們是透過 CAS完成原子性。
JDK提供鎖分為兩種: synchronized依賴 JVM實現鎖,該關鍵字作用物件的作用範圍內同一時刻只能有一個執行緒進行操作。另一種 LOCK,是 JDK提供的程式碼層面的鎖,依賴 CPU指令,代表性是 ReentrantLock

2)可見性:

一個執行緒對主記憶體的修改及時被其他執行緒看到。

JVM提供了 synchronizedvolatilevolatile的可見性是透過記憶體屏障和禁止重排序實現的, volatile會在寫操作時,在寫操作後加一條 store屏障指令,將本地記憶體中的共享變數值重新整理到主記憶體;會在讀操作時,在讀操作前加一條 load指令,從記憶體中讀取共享變數。

3)有序性:

指令沒有被編譯器重排序。

可透過 volatile、synchronized、Lock保證有序性。

10、兩個程式同時要求寫或者讀,能不能實現?如何防止程式的同步?

我認為可以實現,比如兩個程式都讀取日曆程式資料是沒有問題,但同時寫,應該會有衝突。

可以使用共享記憶體實現程式間資料共享。

11、執行緒間操作List

多執行緒數量的問題,一般情況下,多執行緒數量要等於機器 CPU核數 -1.

1.如何讓n個執行緒順序遍歷含有n個元素的List集合
import java.util.ArrayList;import java.util.List;import org.apache.commons.lang3.ArrayUtils;public class Test_4 {    /**
     * 多執行緒處理list
     *
     * @param data  資料list
     * @param threadNum  執行緒數
     */
    public synchronized void handleList(List<String> data, int threadNum) {        int length = data.size();        int tl = length % threadNum == 0 ? length / threadNum : (length
                / threadNum + 1);        for (int i = 0; i < threadNum; i++) {            int end = (i + 1) * tl;
            HandleThread thread = new HandleThread("執行緒[" + (i + 1) + "] ",  data, i * tl, end > length ? length : end);
            thread.start();
        }
    }    class HandleThread extends Thread {        private String threadName;        private List<String> data;        private int start;        private int end;        public HandleThread(String threadName, List<String> data, int start, int end) {            this.threadName = threadName;            this.data = data;            this.start = start;            this.end = end;
        }        public void run() {
            List<String> subList = data.subList(start, end)/*.add("^&*")*/;
            System.out.println(threadName+"處理了"+subList.size()+"條!");
        }
    }    public static void main(String[] args) {
        Test_4 test = new Test_4();        // 準備資料
        List<String> data = new ArrayList<String>();        for (int i = 0; i < 6666; i++) {
            data.add("item" + i);
        }
        test.handleList(data, 5);
        System.out.println(ArrayUtils.toString(data));
    }
}
2. List多執行緒併發讀取讀取現有的list物件
//測試讀取List的執行緒類,大概34秒package com.thread.list;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;public class Main {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        Map<Long,Integer> map = new HashMap<Long,Integer>();        for(int i = 0;i<1000;i++){            list.add(""+i);
        }        int pcount = Runtime.getRuntime().availableProcessors();        
        long start = System.currentTimeMillis();        
        for(int i=0;i<pcount;i++){
           Thread t = new MyThread1(list,map);            map.put(t.getId(),Integer.valueOf(i));
            t.start();            try {
                t.join();
            } catch (InterruptedException e) {              
                e.printStackTrace();
            }            
           // System.out.println(list.get(i));
        }        
        System.out.println("----"+(System.currentTimeMillis() - start));
    }    
}//執行緒類package com.thread.list;import java.util.List;import java.util.Map;public class MyThread1 extends Thread {
    private List<String> list;    private Map<Long,Integer> map;    public MyThread1(List<String> list,Map<Long,Integer> map){        this.list = list;        this.map = map;
    }
    @Override    public void run() {        int pcount = Runtime.getRuntime().availableProcessors();        int i = map.get(Thread.currentThread().getId());        for(;i<list.size();i+=pcount){
            System.out.println(list.get(i));
        }              
    }    
}
3.多執行緒分段處理List集合

場景:大資料 List集合,需要對 List集合中的資料同標準庫中資料進行對比,生成新增,更新,取消資料
解決方案:

List集合分段,
動態建立執行緒池 newFixedThreadPool
將對比操作在多執行緒中實現

public static void main(String[] args) throws Exception {        // 開始時間
        long start = System.currentTimeMillis();
        List<String> list = new ArrayList<String>();        for (int i = 1; i <= 3000; i++) {
            list.add(i + "");
        }        // 每500條資料開啟一條執行緒
        int threadSize = 500;        // 總資料條數
        int dataSize = list.size();        // 執行緒數
        int threadNum = dataSize / threadSize + 1;        // 定義標記,過濾threadNum為整數
        boolean special = dataSize % threadSize == 0;        // 建立一個執行緒池
        ExecutorService exec = Executors.newFixedThreadPool(threadNum);        // 定義一個任務集合
        List<Callable<Integer>> tasks = new ArrayList<Callable<Integer>>();
        Callable<Integer> task = null;
        List<String> cutList = null;        // 確定每條執行緒的資料
        for (int i = 0; i < threadNum; i++) {            if (i == threadNum - 1) {                if (special) {                    break;
                }
                cutList = list.subList(threadSize * i, dataSize);
            } else {
                cutList = list.subList(threadSize * i, threadSize * (i + 1));
            }            // System.out.println("第" + (i + 1) + "組:" + cutList.toString());
            final List<String> listStr = cutList;
            task = new Callable<Integer>() {                @Override
                public Integer call() throws Exception {
                    System.out.println(Thread.currentThread().getName() + "執行緒:" + listStr);                    return 1;
                }
            };            // 這裡提交的任務容器列表和返回的Future列表存在順序對應的關係
            tasks.add(task);
        }
        List<Future<Integer>> results = exec.invokeAll(tasks);        for (Future<Integer> future : results) {
            System.out.println(future.get());
        }        // 關閉執行緒池
        exec.shutdown();
        System.out.println("執行緒任務執行結束");
        System.err.println("執行任務消耗了 :" + (System.currentTimeMillis() - start) + "毫秒");
    }

12、Java中物件的生命週期

1)建立階段(Created):

為物件分配儲存空間,開始構造物件,從超類到子類對 static成員初始化;超類成員變數按順序初始化,遞迴呼叫超類的構造方法,子類成員變數按順序初始化,子類構造方法呼叫。

2)應用階段(In Use):

物件至少被一個強引用持有著。

3)不可見階段(Invisible):

程式執行已超出物件作用域

4)不可達階段(Unreachable):

該物件不再被強引用所持有

5)收集階段(Collected):

假設該物件重寫了 finalize()方法且未執行過,會去執行該方法。

6)終結階段(Finalized):

物件執行完 finalize()方法仍處於不可達狀態,等待垃圾回收器對該物件空間進行回收。

7)物件空間重新分配階段(De-allocated):

垃圾回收器對該物件所佔用的記憶體空間進行回收或再分配,該物件徹底消失。

13、static synchronized 方法的多執行緒訪問和作用

static synchronized控制的是類的所有例項訪問,不管 new了多少物件,只有一份,所以對該類的所有物件都加了鎖。限制多執行緒中該類的所有例項同時訪問 JVM中該類對應的程式碼。

14、同一個類裡面兩個synchronized方法,兩個執行緒同時訪問的問題

如果 synchronized修飾的是靜態方法,鎖的是當前類的 class物件,進入同步程式碼前要獲得當前類物件的鎖;

普通方法,鎖的是當前例項物件,進入同步程式碼前要獲得的是當前例項的鎖;

同步程式碼塊,鎖的是括號裡面的物件,對給定的物件加鎖,進入同步程式碼塊庫前要獲得給定物件鎖;

如果兩個執行緒訪問同一個物件的 synchronized方法,會出現競爭,如果是不同物件,則不會相互影響。

15、volatile的原理

volatile變數修飾的共享變數進行寫操作的時候會多一條彙編程式碼, lock addl $0x0,lock字首的指令在多核處理器下會將當前處理器快取行的資料會寫回到系統記憶體,這個寫回記憶體的操作會引起在其他 CPU裡快取了該記憶體地址的資料無效。同時 lock字首也相當於一個記憶體屏障,對記憶體操作順序進行了限制。

16、synchronized原理

synchronized透過物件的物件頭 (markword)來實現鎖機制, java每個物件都有物件頭,都可以為 synchronized實現提供基礎,都可以作為鎖物件,在位元組碼層面 synchronized塊是透過插入 monitorenter monitorexit完成同步的。持有 monitor物件,透過進入、退出這個 Monitor物件來實現鎖機制。

17、談談NIO的理解

NIO( New Input/ Output)引入了一種基於通道和緩衝區的 I/O方式,它可以使用  Native函式庫直接分配堆外記憶體,然後透過一個儲存在 Java 堆的  DirectByteBuffer物件作為這塊記憶體的引用進行操作,避免了在 Java 堆和 Native堆中來回複製資料。  NIO是一種同步非阻塞的  IO模型。同步是指執行緒不斷輪詢 IO事件是否就緒,非阻塞是指執行緒在等待 IO 的時候,可以同時做其他任務。同步的核心就是 Selector,Selector代替了執行緒本身輪詢 IO事件,避免了阻塞同時減少了不必要的執行緒消耗;非阻塞的核心就是通道和緩衝區,當 IO事件就緒時,可以透過寫道緩衝區,保證 IO的成功,而無需執行緒阻塞式地等待。

18.ReentrantLock 、Lock、synchronized和volatile比較

1)volatile:

解決變數在多個執行緒間的可見性,但不能保證原子性,只能用於修飾變數,不會發生阻塞。 volatile能遮蔽編譯指令重排,不會把其後面的指令排到記憶體屏障之前的位置,也不會把前面的指令排到記憶體屏障的後面。多用於平行計算的單例模式。 volatile規定 CPU每次都必須從記憶體讀取資料,不能從 CPU快取中讀取,保證了多執行緒在多 CPU計算中永遠拿到的都是最新的值。

2)synchronized:

互斥鎖,操作互斥,併發執行緒過來,序列獲得鎖,序列執行程式碼。解決的是多個執行緒間訪問共享資源的同步性,可保證原子性,也可間接保證可見性,因為它會將私有記憶體和公有記憶體中的資料做同步。可用來修飾方法、程式碼塊。會出現阻塞。 synchronized發生異常時,會自動釋放執行緒佔有的鎖,因此不會導致死鎖現象發生。非公平鎖,每次都是相互爭搶資源。

3)lock

是一個介面,而 synchronizedjava中的關鍵字, synchronized是內建語言的實現。 lock可以讓等待鎖的執行緒響應中斷。在發生異常時,如果沒有主動透過 unLock()去釋放鎖,則可能造成死鎖現象,因此使用 Lock時需要在 finally塊中釋放鎖。

4)ReentrantLock

可重入鎖,鎖的分配機制是基於執行緒的分配,而不是基於方法呼叫的分配。 ReentrantLocktryLock方法,如果鎖被其他執行緒持有,返回 false,可避免形成死鎖。對程式碼加鎖的顆粒會更小,更節省資源,提高程式碼效能。 ReentrantLock可實現公平鎖和非公平鎖,公平鎖就是先來的先獲取資源。 ReentrantReadWriteLock用於讀多寫少的場合,且讀不需要互斥場景。

友情推薦:

敲開阿里大門的執行緒、多執行緒和執行緒池面試專題 Android學習PDF_原始碼筆記_面試文件_進階影片.pdf



來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69952849/viewspace-2664599/,如需轉載,請註明出處,否則將追究法律責任。

相關文章