這篇“Android的線程、多線程和線程池面試題有哪些”文章的知識(shí)點(diǎn)大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價(jià)值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Android的線程、多線程和線程池面試題有哪些”文章吧。
我們提供的服務(wù)有:成都網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計(jì)、微信公眾號(hào)開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、寧武ssl等。為成百上千企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的寧武網(wǎng)站制作公司
1)繼承Thread
類,重寫run()
方法,在run()
方法體中編寫要完成的任務(wù) new Thread().start();
2)實(shí)現(xiàn)Runnable
接口,實(shí)現(xiàn)run()
方法 new Thread(new MyRunnable()).start();
3)實(shí)現(xiàn)Callable
接口MyCallable
類,實(shí)現(xiàn)call()
方法,使用FutureTask
類來包裝Callable
對(duì)象,使用FutureTask
對(duì)象作為Thread
對(duì)象的target
創(chuàng)建并啟動(dòng)線程;調(diào)用FutureTask
對(duì)象的get()
方法來獲得子線程執(zhí)行結(jié)束后的返回值。
FutureTaskft = new FutureTask (new MyCallable());new Thread(ft).start();
run()
方法只是線程的主體方法,和普通方法一樣,不會(huì)創(chuàng)建新的線程。
只有調(diào)用start()
方法,才會(huì)啟動(dòng)一個(gè)新的線程,新線程才會(huì)調(diào)用run()
方法,線程才會(huì)開始執(zhí)行。
創(chuàng)建Semaphore
變量,Semaphore semaphore = new Semaphore(5, true);
當(dāng)方法進(jìn)入時(shí),請(qǐng)求一個(gè)信號(hào),如果信號(hào)被用完則等待,方法運(yùn)行完,釋放一個(gè)信號(hào),釋放的信號(hào)新的線程就可以使用。
wait()
方法屬于Object
類,調(diào)用該方法時(shí),線程會(huì)放棄對(duì)象鎖,只有該對(duì)象調(diào)用notify()
方法后本線程才進(jìn)入對(duì)象鎖定池準(zhǔn)備獲取對(duì)象鎖進(jìn)入運(yùn)行狀態(tài)。
sleep()
方法屬于Thread
類,sleep()
導(dǎo)致程序暫停執(zhí)行指定的時(shí)間,讓出CPU
,但它的監(jiān)控狀態(tài)依然保存著,當(dāng)指定時(shí)間到了又會(huì)回到運(yùn)行狀態(tài),sleep()
方法中線程不會(huì)釋放對(duì)象鎖。
notify:
喚醒在此對(duì)象監(jiān)視器上等待的單個(gè)線程
notifyAll():
通知所有等待該競(jìng)爭資源的線程
wait:
釋放obj
的鎖,導(dǎo)致當(dāng)前的線程等待,直接其他線程調(diào)用此對(duì)象的notify()
或notifyAll()
方法
當(dāng)要調(diào)用wait()
或notify()/notifyAll()
方法時(shí),一定要對(duì)競(jìng)爭資源進(jìn)行加鎖,一般放到synchronized(obj)
代碼中。當(dāng)調(diào)用obj.notify/notifyAll
后,調(diào)用線程依舊持有obj
鎖,因此等待線程雖被喚醒,但仍無法獲得obj
鎖,直到調(diào)用線程退出synchronized
塊,釋放obj
鎖后,其他等待線程才有機(jī)會(huì)獲得鎖繼續(xù)執(zhí)行。
1)線程執(zhí)行了Thread.sleep(int millsecond)
方法,放棄CPU
,睡眠一段時(shí)間,一段時(shí)間過后恢復(fù)執(zhí)行;
2)線程執(zhí)行一段同步代碼,但無法獲得相關(guān)的同步鎖,只能進(jìn)入阻塞狀態(tài),等到獲取到同步鎖,才能恢復(fù)執(zhí)行;
3)線程執(zhí)行了一個(gè)對(duì)象的wait()
方法,直接進(jìn)入阻塞態(tài),等待其他線程執(zhí)行notify()/notifyAll()
操作;
4)線程執(zhí)行某些IO
操作,因?yàn)榈却嚓P(guān)資源而進(jìn)入了阻塞態(tài),如System.in
,但沒有收到鍵盤的輸入,則進(jìn)入阻塞態(tài)。
5)線程禮讓,Thread.yield()
方法,暫停當(dāng)前正在執(zhí)行的線程對(duì)象,把執(zhí)行機(jī)會(huì)讓給相同或更高優(yōu)先級(jí)的線程,但并不會(huì)使線程進(jìn)入阻塞態(tài),線程仍處于可執(zhí)行態(tài),隨時(shí)可能再次分得CPU
時(shí)間。線程自閉,join()
方法,在當(dāng)前線程調(diào)用另一個(gè)線程的join()
方法,則當(dāng)前線程進(jìn)入阻塞態(tài),直到另一個(gè)線程運(yùn)行結(jié)束,當(dāng)前線程再由阻塞轉(zhuǎn)為就緒態(tài)。
6)線程執(zhí)行suspend()
使線程進(jìn)入阻塞態(tài),必須resume()
方法被調(diào)用,才能使線程重新進(jìn)入可執(zhí)行狀態(tài)。
使用標(biāo)志位
2)使用stop()
方法,但該方法就像關(guān)掉電腦電源一樣,可能會(huì)發(fā)生預(yù)料不到的問題
3)使用中斷interrupt()
public class Thread { // 中斷當(dāng)前線程 public void interrupt(); // 判斷當(dāng)前線程是否被中斷 public boolen isInterrupt(); // 清除當(dāng)前線程的中斷狀態(tài),并返回之前的值 public static boolen interrupted(); }
但調(diào)用interrupt()
方法只是傳遞中斷請(qǐng)求消息,并不代表要立馬停止目標(biāo)線程。
之所以需要同步,因?yàn)樵诙嗑€程并發(fā)控制,當(dāng)多個(gè)線程同時(shí)操作一個(gè)可共享的資源時(shí),如果沒有采取同步機(jī)制,將會(huì)導(dǎo)致數(shù)據(jù)不準(zhǔn)確,因此需要加入同步鎖,確保在該線程沒有完成操作前被其他線程調(diào)用,從而保證該變量的唯一一性和準(zhǔn)確性。
由于java
的每個(gè)對(duì)象都有一個(gè)內(nèi)置鎖,用此關(guān)鍵字修飾方法時(shí),內(nèi)置鎖會(huì)保護(hù)整個(gè)方法。在調(diào)用該方法前,需獲得內(nèi)置鎖,否則就處于陰塞狀態(tài)。
保證變量在線程間的可見性,每次線程要訪問volatile
修飾的變量時(shí)都從內(nèi)存中讀取,而不緩存中,這樣每個(gè)線程訪問到的變量都是一樣的。且使用內(nèi)存屏障。
創(chuàng)建一個(gè)ReentrantLock
實(shí)例
lock()
獲得鎖unlock()
釋放鎖
每個(gè)線程都會(huì)保存一份該變量的副本,副本之間相互獨(dú)立,這樣每個(gè)線程都可以隨意修改自己的副本,而不影響其他線程。常用方法ThreadLocal()
創(chuàng)建一個(gè)線程本地變量;get()
返回此線程局部的當(dāng)前線程副本變量;initialValue()
返回此線程局部變量的當(dāng)前線程的初始值;set(T value)
將此線程變量的當(dāng)前線程副本中的值設(shè)置為value
如AtomicInteger
,常用方法AtomicInteger(int value)
創(chuàng)建個(gè)有給定初始值的AtomicInteger
整數(shù);addAndGet(int data)
以原子方式將給定值與當(dāng)前值相加
例如LinkedBlockingQueue
線程安全性體現(xiàn)在三方法:
提供互斥訪問,同一時(shí)刻只能有一個(gè)線和至數(shù)據(jù)進(jìn)行操作。
JDK
中提供了很多atomic
類,如AtomicInteger\AtomicBoolean\AtomicLong
,它們是通過CAS
完成原子性。JDK
提供鎖分為兩種:synchronized
依賴JVM
實(shí)現(xiàn)鎖,該關(guān)鍵字作用對(duì)象的作用范圍內(nèi)同一時(shí)刻只能有一個(gè)線程進(jìn)行操作。另一種LOCK
,是JDK
提供的代碼層面的鎖,依賴CPU
指令,代表性是ReentrantLock
。
一個(gè)線程對(duì)主內(nèi)存的修改及時(shí)被其他線程看到。
JVM
提供了synchronized
和volatile
,volatile
的可見性是通過內(nèi)存屏障和禁止重排序?qū)崿F(xiàn)的,volatile
會(huì)在寫操作時(shí),在寫操作后加一條store
屏障指令,將本地內(nèi)存中的共享變量值刷新到主內(nèi)存;會(huì)在讀操作時(shí),在讀操作前加一條load
指令,從內(nèi)存中讀取共享變量。
指令沒有被編譯器重排序。
可通過volatile、synchronized、Lock
保證有序性。
我認(rèn)為可以實(shí)現(xiàn),比如兩個(gè)進(jìn)程都讀取日歷進(jìn)程數(shù)據(jù)是沒有問題,但同時(shí)寫,應(yīng)該會(huì)有沖突。
可以使用共享內(nèi)存實(shí)現(xiàn)進(jìn)程間數(shù)據(jù)共享。
多線程數(shù)量的問題,一般情況下,多線程數(shù)量要等于機(jī)器CPU
核數(shù)-1
.
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(Listdata, 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 data; private int start; private int end; public HandleThread(String threadName, List data, int start, int end) { this.threadName = threadName; this.data = data; this.start = start; this.end = end; } public void run() { List subList = data.subList(start, end)/*.add("^&*")*/; System.out.println(threadName+"處理了"+subList.size()+"條!"); } } public static void main(String[] args) { Test_4 test = new Test_4(); // 準(zhǔn)備數(shù)據(jù) List data = new ArrayList (); for (int i = 0; i < 6666; i++) { data.add("item" + i); } test.handleList(data, 5); System.out.println(ArrayUtils.toString(data)); } }
//測(cè)試讀取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) { Listlist = new ArrayList (); Map map = new HashMap (); for(int i = 0;i<1000;i++){ list.add(""+i); } int pcount = Runtime.getRuntime().availableProcessors(); long start = System.currentTimeMillis(); for(int i=0;i list; private Map map; public MyThread1(List list,Map 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 3.多線程分段處理List集合 場(chǎng)景:大數(shù)據(jù)
List
集合,需要對(duì)List
集合中的數(shù)據(jù)同標(biāo)準(zhǔn)庫中數(shù)據(jù)進(jìn)行對(duì)比,生成新增,更新,取消數(shù)據(jù)
解決方案:
List
集合分段,
動(dòng)態(tài)創(chuàng)建線程池newFixedThreadPool
將對(duì)比操作在多線程中實(shí)現(xiàn)public static void main(String[] args) throws Exception { // 開始時(shí)間 long start = System.currentTimeMillis(); Listlist = new ArrayList (); 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; // 定義標(biāo)記,過濾threadNum為整數(shù) boolean special = dataSize % threadSize == 0; // 創(chuàng)建一個(gè)線程池 ExecutorService exec = Executors.newFixedThreadPool(threadNum); // 定義一個(gè)任務(wù)集合 List > tasks = new ArrayList >(); Callable task = null; List 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 listStr = cutList; task = new Callable () { @Override public Integer call() throws Exception { System.out.println(Thread.currentThread().getName() + "線程:" + listStr); return 1; } }; // 這里提交的任務(wù)容器列表和返回的Future列表存在順序?qū)?yīng)的關(guān)系 tasks.add(task); } List > results = exec.invokeAll(tasks); for (Future future : results) { System.out.println(future.get()); } // 關(guān)閉線程池 exec.shutdown(); System.out.println("線程任務(wù)執(zhí)行結(jié)束"); System.err.println("執(zhí)行任務(wù)消耗了 :" + (System.currentTimeMillis() - start) + "毫秒"); } 12、Java中對(duì)象的生命周期
1)創(chuàng)建階段(Created): 為對(duì)象分配存儲(chǔ)空間,開始構(gòu)造對(duì)象,從超類到子類對(duì)
static
成員初始化;超類成員變量按順序初始化,遞歸調(diào)用超類的構(gòu)造方法,子類成員變量按順序初始化,子類構(gòu)造方法調(diào)用。2)應(yīng)用階段(In Use): 對(duì)象至少被一個(gè)強(qiáng)引用持有著。
3)不可見階段(Invisible): 程序運(yùn)行已超出對(duì)象作用域
4)不可達(dá)階段(Unreachable): 該對(duì)象不再被強(qiáng)引用所持有
5)收集階段(Collected): 假設(shè)該對(duì)象重寫了
finalize()
方法且未執(zhí)行過,會(huì)去執(zhí)行該方法。6)終結(jié)階段(Finalized): 對(duì)象運(yùn)行完
finalize()
方法仍處于不可達(dá)狀態(tài),等待垃圾回收器對(duì)該對(duì)象空間進(jìn)行回收。7)對(duì)象空間重新分配階段(De-allocated): 垃圾回收器對(duì)該對(duì)象所占用的內(nèi)存空間進(jìn)行回收或再分配,該對(duì)象徹底消失。
13、static synchronized 方法的多線程訪問和作用
static synchronized
控制的是類的所有實(shí)例訪問,不管new
了多少對(duì)象,只有一份,所以對(duì)該類的所有對(duì)象都加了鎖。限制多線程中該類的所有實(shí)例同時(shí)訪問JVM
中該類對(duì)應(yīng)的代碼。14、同一個(gè)類里面兩個(gè)synchronized方法,兩個(gè)線程同時(shí)訪問的問題
如果
synchronized
修飾的是靜態(tài)方法,鎖的是當(dāng)前類的class
對(duì)象,進(jìn)入同步代碼前要獲得當(dāng)前類對(duì)象的鎖;普通方法,鎖的是當(dāng)前實(shí)例對(duì)象,進(jìn)入同步代碼前要獲得的是當(dāng)前實(shí)例的鎖;
同步代碼塊,鎖的是括號(hào)里面的對(duì)象,對(duì)給定的對(duì)象加鎖,進(jìn)入同步代碼塊庫前要獲得給定對(duì)象鎖;
如果兩個(gè)線程訪問同一個(gè)對(duì)象的
synchronized
方法,會(huì)出現(xiàn)競(jìng)爭,如果是不同對(duì)象,則不會(huì)相互影響。15、volatile的原理
有
volatile
變量修飾的共享變量進(jìn)行寫操作的時(shí)候會(huì)多一條匯編代碼,lock addl $0x0,lock
前綴的指令在多核處理器下會(huì)將當(dāng)前處理器緩存行的數(shù)據(jù)會(huì)寫回到系統(tǒng)內(nèi)存,這個(gè)寫回內(nèi)存的操作會(huì)引起在其他CPU
里緩存了該內(nèi)存地址的數(shù)據(jù)無效。同時(shí)lock
前綴也相當(dāng)于一個(gè)內(nèi)存屏障,對(duì)內(nèi)存操作順序進(jìn)行了限制。16、synchronized原理
synchronized
通過對(duì)象的對(duì)象頭(markword)
來實(shí)現(xiàn)鎖機(jī)制,java
每個(gè)對(duì)象都有對(duì)象頭,都可以為synchronized
實(shí)現(xiàn)提供基礎(chǔ),都可以作為鎖對(duì)象,在字節(jié)碼層面synchronized
塊是通過插入monitorenter monitorexit
完成同步的。持有monitor
對(duì)象,通過進(jìn)入、退出這個(gè)Monitor
對(duì)象來實(shí)現(xiàn)鎖機(jī)制。17、談?wù)凬IO的理解
NIO( New Input/ Output)
引入了一種基于通道和緩沖區(qū)的I/O
方式,它可以使用Native
函數(shù)庫直接分配堆外內(nèi)存,然后通過一個(gè)存儲(chǔ)在Java
堆的DirectByteBuffer
對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作,避免了在Java
堆和Native
堆中來回復(fù)制數(shù)據(jù)。NIO
是一種同步非阻塞的IO
模型。同步是指線程不斷輪詢IO
事件是否就緒,非阻塞是指線程在等待IO
的時(shí)候,可以同時(shí)做其他任務(wù)。同步的核心就是Selector,Selector
代替了線程本身輪詢IO
事件,避免了阻塞同時(shí)減少了不必要的線程消耗;非阻塞的核心就是通道和緩沖區(qū),當(dāng)IO
事件就緒時(shí),可以通過寫道緩沖區(qū),保證IO
的成功,而無需線程阻塞式地等待。18.ReentrantLock 、Lock、synchronized和volatile比較
1)volatile:
解決變量在多個(gè)線程間的可見性,但不能保證原子性,只能用于修飾變量,不會(huì)發(fā)生阻塞。
volatile
能屏蔽編譯指令重排,不會(huì)把其后面的指令排到內(nèi)存屏障之前的位置,也不會(huì)把前面的指令排到內(nèi)存屏障的后面。多用于并行計(jì)算的單例模式。volatile
規(guī)定CPU
每次都必須從內(nèi)存讀取數(shù)據(jù),不能從CPU
緩存中讀取,保證了多線程在多CPU
計(jì)算中永遠(yuǎn)拿到的都是最新的值。2)synchronized:
互斥鎖,操作互斥,并發(fā)線程過來,串行獲得鎖,串行執(zhí)行代碼。解決的是多個(gè)線程間訪問共享資源的同步性,可保證原子性,也可間接保證可見性,因?yàn)樗鼤?huì)將私有內(nèi)存和公有內(nèi)存中的數(shù)據(jù)做同步??捎脕硇揎椃椒ā⒋a塊。會(huì)出現(xiàn)阻塞。
synchronized
發(fā)生異常時(shí),會(huì)自動(dòng)釋放線程占有的鎖,因此不會(huì)導(dǎo)致死鎖現(xiàn)象發(fā)生。非公平鎖,每次都是相互爭搶資源。3)lock
是一個(gè)接口,而
synchronized
是java
中的關(guān)鍵字,synchronized
是內(nèi)置語言的實(shí)現(xiàn)。lock
可以讓等待鎖的線程響應(yīng)中斷。在發(fā)生異常時(shí),如果沒有主動(dòng)通過unLock()
去釋放鎖,則可能造成死鎖現(xiàn)象,因此使用Lock
時(shí)需要在finally
塊中釋放鎖。4)ReentrantLock
可重入鎖,鎖的分配機(jī)制是基于線程的分配,而不是基于方法調(diào)用的分配。
ReentrantLock
有tryLock
方法,如果鎖被其他線程持有,返回false
,可避免形成死鎖。對(duì)代碼加鎖的顆粒會(huì)更小,更節(jié)省資源,提高代碼性能。ReentrantLock
可實(shí)現(xiàn)公平鎖和非公平鎖,公平鎖就是先來的先獲取資源。ReentrantReadWriteLock
用于讀多寫少的場(chǎng)合,且讀不需要互斥場(chǎng)景。以上就是關(guān)于“Android的線程、多線程和線程池面試題有哪些”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對(duì)大家有幫助,若想了解更多相關(guān)的知識(shí)內(nèi)容,請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。
網(wǎng)站名稱:Android的線程、多線程和線程池面試題有哪些
網(wǎng)站網(wǎng)址:http://weahome.cn/article/iepici.html