溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Android的線程、多線程和線程池面試題有哪些

發(fā)布時間:2022-01-12 11:02:37 來源:億速云 閱讀:251 作者:iii 欄目:移動開發(fā)

這篇“Android的線程、多線程和線程池面試題有哪些”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Android的線程、多線程和線程池面試題有哪些”文章吧。

1、開啟線程的三種方式?

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

2)實現(xiàn)Runnable接口,實現(xiàn)run()方法 new Thread(new MyRunnable()).start();

3)實現(xiàn)Callable接口MyCallable類,實現(xiàn)call()方法,使用FutureTask類來包裝Callable對象,使用FutureTask對象作為Thread對象的target創(chuàng)建并啟動線程;調用FutureTask對象的get()方法來獲得子線程執(zhí)行結束后的返回值。

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

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

run()方法只是線程的主體方法,和普通方法一樣,不會創(chuàng)建新的線程。
只有調用start()方法,才會啟動一個新的線程,新線程才會調用run()方法,線程才會開始執(zhí)行。

3、如何控制某個方法允許并發(fā)訪問線程的個數(shù)?

創(chuàng)建Semaphore變量,Semaphore semaphore = new Semaphore(5, true);當方法進入時,請求一個信號,如果信號被用完則等待,方法運行完,釋放一個信號,釋放的信號新的線程就可以使用。

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

wait()方法屬于Object類,調用該方法時,線程會放棄對象鎖,只有該對象調用notify()方法后本線程才進入對象鎖定池準備獲取對象鎖進入運行狀態(tài)。

sleep()方法屬于Thread類,sleep()導致程序暫停執(zhí)行指定的時間,讓出CPU,但它的監(jiān)控狀態(tài)依然保存著,當指定時間到了又會回到運行狀態(tài),sleep()方法中線程不會釋放對象鎖。

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

notify:
喚醒在此對象監(jiān)視器上等待的單個線程

notifyAll():
通知所有等待該競爭資源的線程

wait:
釋放obj的鎖,導致當前的線程等待,直接其他線程調用此對象的notify()notifyAll()方法

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

6、什么導致線程阻塞?

(1)一般線程阻塞

1)線程執(zhí)行了Thread.sleep(int millsecond)方法,放棄CPU,睡眠一段時間,一段時間過后恢復執(zhí)行;

2)線程執(zhí)行一段同步代碼,但無法獲得相關的同步鎖,只能進入阻塞狀態(tài),等到獲取到同步鎖,才能恢復執(zhí)行;

3)線程執(zhí)行了一個對象的wait()方法,直接進入阻塞態(tài),等待其他線程執(zhí)行notify()/notifyAll()操作;

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

5)線程禮讓,Thread.yield()方法,暫停當前正在執(zhí)行的線程對象,把執(zhí)行機會讓給相同或更高優(yōu)先級的線程,但并不會使線程進入阻塞態(tài),線程仍處于可執(zhí)行態(tài),隨時可能再次分得CPU時間。線程自閉,join()方法,在當前線程調用另一個線程的join()方法,則當前線程進入阻塞態(tài),直到另一個線程運行結束,當前線程再由阻塞轉為就緒態(tài)。

6)線程執(zhí)行suspend()使線程進入阻塞態(tài),必須resume()方法被調用,才能使線程重新進入可執(zhí)行狀態(tài)。

7?線程如何關閉?

  1. 使用標志位

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

3)使用中斷interrupt()

public class Thread {
    // 中斷當前線程
    public void interrupt();    // 判斷當前線程是否被中斷
    public boolen isInterrupt();    // 清除當前線程的中斷狀態(tài),并返回之前的值
    public static boolen interrupted();   
}

但調用interrupt()方法只是傳遞中斷請求消息,并不代表要立馬停止目標線程。

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

之所以需要同步,因為在多線程并發(fā)控制,當多個線程同時操作一個可共享的資源時,如果沒有采取同步機制,將會導致數(shù)據(jù)不準確,因此需要加入同步鎖,確保在該線程沒有完成操作前被其他線程調用,從而保證該變量的唯一一性和準確性。

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

由于java的每個對象都有一個內置鎖,用此關鍵字修飾方法時,內置鎖會保護整個方法。在調用該方法前,需獲得內置鎖,否則就處于陰塞狀態(tài)。

2)volatile修飾變量

保證變量在線程間的可見性,每次線程要訪問volatile修飾的變量時都從內存中讀取,而不緩存中,這樣每個線程訪問到的變量都是一樣的。且使用內存屏障。

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

創(chuàng)建一個ReentrantLock實例

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

4)使用局部變量ThreadLocal實現(xiàn)線程同步

每個線程都會保存一份該變量的副本,副本之間相互獨立,這樣每個線程都可以隨意修改自己的副本,而不影響其他線程。常用方法ThreadLocal()創(chuàng)建一個線程本地變量;get()返回此線程局部的當前線程副本變量;initialValue()返回此線程局部變量的當前線程的初始值;set(T value)將此線程變量的當前線程副本中的值設置為value

5) 使用原子變量

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

6)使用阻塞隊列實現(xiàn)線程同步

例如LinkedBlockingQueue<E>

9、如何保證線程安全?

線程安全性體現(xiàn)在三方法:

1)原子性:

提供互斥訪問,同一時刻只能有一個線和至數(shù)據(jù)進行操作。

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

2)可見性:

一個線程對主內存的修改及時被其他線程看到。

JVM提供了synchronizedvolatilevolatile的可見性是通過內存屏障和禁止重排序實現(xiàn)的,volatile會在寫操作時,在寫操作后加一條store屏障指令,將本地內存中的共享變量值刷新到主內存;會在讀操作時,在讀操作前加一條load指令,從內存中讀取共享變量。

3)有序性:

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

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

10、兩個進程同時要求寫或者讀,能不能實現(xiàn)?如何防止進程的同步?

我認為可以實現(xiàn),比如兩個進程都讀取日歷進程數(shù)據(jù)是沒有問題,但同時寫,應該會有沖突。

可以使用共享內存實現(xiàn)進程間數(shù)據(jù)共享。

11、線程間操作List

多線程數(shù)量的問題,一般情況下,多線程數(shù)量要等于機器CPU核數(shù)-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  數(shù)據(jù)list
     * @param threadNum  線程數(shù)
     */
    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();        // 準備數(shù)據(jù)
        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多線程并發(fā)讀取讀取現(xiàn)有的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集合

場景:大數(shù)據(jù)List集合,需要對List集合中的數(shù)據(jù)同標準庫中數(shù)據(jù)進行對比,生成新增,更新,取消數(shù)據(jù)
解決方案:

List集合分段,
動態(tài)創(chuàng)建線程池newFixedThreadPool
將對比操作在多線程中實現(xiàn)

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條數(shù)據(jù)開啟一條線程
        int threadSize = 500;        // 總數(shù)據(jù)條數(shù)
        int dataSize = list.size();        // 線程數(shù)
        int threadNum = dataSize / threadSize + 1;        // 定義標記,過濾threadNum為整數(shù)
        boolean special = dataSize % threadSize == 0;        // 創(chuàng)建一個線程池
        ExecutorService exec = Executors.newFixedThreadPool(threadNum);        // 定義一個任務集合
        List<Callable<Integer>> tasks = new ArrayList<Callable<Integer>>();
        Callable<Integer> task = null;
        List<String> cutList = null;        // 確定每條線程的數(shù)據(jù)
        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("線程任務執(zhí)行結束");
        System.err.println("執(zhí)行任務消耗了 :" + (System.currentTimeMillis() - start) + "毫秒");
    }

12、Java中對象的生命周期

1)創(chuàng)建階段(Created):

為對象分配存儲空間,開始構造對象,從超類到子類對static成員初始化;超類成員變量按順序初始化,遞歸調用超類的構造方法,子類成員變量按順序初始化,子類構造方法調用。

2)應用階段(In Use):

對象至少被一個強引用持有著。

3)不可見階段(Invisible):

程序運行已超出對象作用域

4)不可達階段(Unreachable):

該對象不再被強引用所持有

5)收集階段(Collected):

假設該對象重寫了finalize()方法且未執(zhí)行過,會去執(zhí)行該方法。

6)終結階段(Finalized):

對象運行完finalize()方法仍處于不可達狀態(tài),等待垃圾回收器對該對象空間進行回收。

7)對象空間重新分配階段(De-allocated):

垃圾回收器對該對象所占用的內存空間進行回收或再分配,該對象徹底消失。

13、static synchronized 方法的多線程訪問和作用

static synchronized控制的是類的所有實例訪問,不管new了多少對象,只有一份,所以對該類的所有對象都加了鎖。限制多線程中該類的所有實例同時訪問JVM中該類對應的代碼。

14、同一個類里面兩個synchronized方法,兩個線程同時訪問的問題

如果synchronized修飾的是靜態(tài)方法,鎖的是當前類的class對象,進入同步代碼前要獲得當前類對象的鎖;

普通方法,鎖的是當前實例對象,進入同步代碼前要獲得的是當前實例的鎖;

同步代碼塊,鎖的是括號里面的對象,對給定的對象加鎖,進入同步代碼塊庫前要獲得給定對象鎖;

如果兩個線程訪問同一個對象的synchronized方法,會出現(xiàn)競爭,如果是不同對象,則不會相互影響。

15、volatile的原理

volatile變量修飾的共享變量進行寫操作的時候會多一條匯編代碼,lock addl $0x0,lock前綴的指令在多核處理器下會將當前處理器緩存行的數(shù)據(jù)會寫回到系統(tǒng)內存,這個寫回內存的操作會引起在其他CPU里緩存了該內存地址的數(shù)據(jù)無效。同時lock前綴也相當于一個內存屏障,對內存操作順序進行了限制。

16、synchronized原理

synchronized通過對象的對象頭(markword)來實現(xiàn)鎖機制,java每個對象都有對象頭,都可以為synchronized實現(xiàn)提供基礎,都可以作為鎖對象,在字節(jié)碼層面synchronized塊是通過插入monitorenter monitorexit完成同步的。持有monitor對象,通過進入、退出這個Monitor對象來實現(xiàn)鎖機制。

17、談談NIO的理解

NIO( New Input/ Output)引入了一種基于通道和緩沖區(qū)的I/O方式,它可以使用 Native函數(shù)庫直接分配堆外內存,然后通過一個存儲在Java 堆的 DirectByteBuffer對象作為這塊內存的引用進行操作,避免了在Java 堆和Native堆中來回復制數(shù)據(jù)。 NIO是一種同步非阻塞的 IO模型。同步是指線程不斷輪詢IO事件是否就緒,非阻塞是指線程在等待IO 的時候,可以同時做其他任務。同步的核心就是Selector,Selector代替了線程本身輪詢IO事件,避免了阻塞同時減少了不必要的線程消耗;非阻塞的核心就是通道和緩沖區(qū),當IO事件就緒時,可以通過寫道緩沖區(qū),保證IO的成功,而無需線程阻塞式地等待。

18.ReentrantLock 、Lock、synchronized和volatile比較

1)volatile:

解決變量在多個線程間的可見性,但不能保證原子性,只能用于修飾變量,不會發(fā)生阻塞。volatile能屏蔽編譯指令重排,不會把其后面的指令排到內存屏障之前的位置,也不會把前面的指令排到內存屏障的后面。多用于并行計算的單例模式。volatile規(guī)定CPU每次都必須從內存讀取數(shù)據(jù),不能從CPU緩存中讀取,保證了多線程在多CPU計算中永遠拿到的都是最新的值。

2)synchronized:

互斥鎖,操作互斥,并發(fā)線程過來,串行獲得鎖,串行執(zhí)行代碼。解決的是多個線程間訪問共享資源的同步性,可保證原子性,也可間接保證可見性,因為它會將私有內存和公有內存中的數(shù)據(jù)做同步。可用來修飾方法、代碼塊。會出現(xiàn)阻塞。synchronized發(fā)生異常時,會自動釋放線程占有的鎖,因此不會導致死鎖現(xiàn)象發(fā)生。非公平鎖,每次都是相互爭搶資源。

3)lock

是一個接口,而synchronizedjava中的關鍵字,synchronized是內置語言的實現(xiàn)。lock可以讓等待鎖的線程響應中斷。在發(fā)生異常時,如果沒有主動通過unLock()去釋放鎖,則可能造成死鎖現(xiàn)象,因此使用Lock時需要在finally塊中釋放鎖。

4)ReentrantLock

可重入鎖,鎖的分配機制是基于線程的分配,而不是基于方法調用的分配。ReentrantLocktryLock方法,如果鎖被其他線程持有,返回false,可避免形成死鎖。對代碼加鎖的顆粒會更小,更節(jié)省資源,提高代碼性能。ReentrantLock可實現(xiàn)公平鎖和非公平鎖,公平鎖就是先來的先獲取資源。ReentrantReadWriteLock用于讀多寫少的場合,且讀不需要互斥場景。

以上就是關于“Android的線程、多線程和線程池面試題有哪些”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內容。

AI